第12章:優化并發_《C++性能優化指南》notes

優化并發

      • 一、并發基礎與優化核心知識點
      • 二、關鍵代碼示例與測試
      • 三、關鍵優化策略總結
      • 四、性能測試方法論
      • 多選題
      • 設計題
      • 答案與詳解
        • 多選題答案:
      • 設計題答案示例

一、并發基礎與優化核心知識點

  1. 線程 vs 異步任務
  • 核心區別std::thread直接管理線程,std::async由運行時決定異步策略(可能用線程池)。
  • 優化點:頻繁創建線程開銷大,優先用 std::async
  1. 原子操作與內存序
  • 原子類型std::atomic<T>確保操作不可分割。
  • 內存序memory_order_relaxed(無同步)到 memory_order_seq_cst(全序同步)。
  1. 鎖的精細控制
  • 鎖類型std::mutexstd::recursive_mutexstd::shared_mutex
  • 優化技巧:縮短臨界區、避免嵌套鎖、用 std::lock_guard/std::unique_lock自動管理。
  1. 條件變量與生產者-消費者
  • 使用模式wait()搭配謂詞防止虛假喚醒,notify_one()/notify_all()通知。
  1. 無鎖數據結構
  • 適用場景:高并發計數器、隊列等,減少鎖競爭。

二、關鍵代碼示例與測試

示例1:原子操作 vs 互斥鎖的性能對比

#include <iostream>
#include <atomic>
#include <mutex>
#include <thread>
#include <vector>
#include <chrono>constexpr int kIncrements = 1'000'000;// 使用互斥鎖的計數器
struct MutexCounter {int value = 0;std::mutex mtx;void increment() {std::lock_guard<std::mutex> lock(mtx);++value;}
};// 使用原子操作的計數器
struct AtomicCounter {std::atomic<int> value{0};void increment() {value.fetch_add(1, std::memory_order_relaxed);}
};template<typename Counter>
void test_performance(const std::string& name) {Counter counter;auto start = std::chrono::high_resolution_clock::now();std::vector<std::thread> threads;for (int i = 0; i < 4; ++i) {threads.emplace_back([&counter] {for (int j = 0; j < kIncrements; ++j) {counter.increment();}});}for (auto& t : threads) t.join();auto end = std::chrono::high_resolution_clock::now();auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();std::cout << name << " Result: " << counter.value << ", Time: " << duration << "ms\n";
}int main() {test_performance<MutexCounter>("Mutex Counter");test_performance<AtomicCounter>("Atomic Counter");return 0;
}

編譯命令

g++ -std=c++11 -O2 -pthread atomic_vs_mutex.cpp -o atomic_vs_mutex

輸出示例

Mutex Counter Result: 4000000, Time: 182ms
Atomic Counter Result: 4000000, Time: 32ms

結論:原子操作在高并發下性能顯著優于互斥鎖。


示例2:線程池實現(減少線程創建開銷)

#include <iostream>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>class ThreadPool {
public:ThreadPool(size_t num_threads) : stop(false) {for (size_t i = 0; i < num_threads; ++i) {workers.emplace_back([this] {while (true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(queue_mutex);condition.wait(lock, [this] { return stop || !tasks.empty(); });if (stop && tasks.empty()) return;task = std::move(tasks.front());tasks.pop();}task();}});}}template<class F, class... Args>auto enqueue(F&& f, Args&&... args) -> std::future<decltype(f(args...))> {using return_type = decltype(f(args...));auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));std::future<return_type> res = task->get_future();{std::unique_lock<std::mutex> lock(queue_mutex);tasks.emplace([task] { (*task)(); });}condition.notify_one();return res;}~ThreadPool() {{std::unique_lock<std::mutex> lock(queue_mutex);stop = true;}condition.notify_all();for (std::thread& worker : workers)worker.join();}private:std::vector<std::thread> workers;std::queue<std::function<void()>> tasks;std::mutex queue_mutex;std::condition_variable condition;bool stop;
};int main() {ThreadPool pool(4);std::vector<std::future<int>> results;for (int i = 0; i < 8; ++i) {results.emplace_back(pool.enqueue([i] {std::cout << "Task " << i << " executed\n";return i * i;}));}for (auto& result : results)std::cout << "Result: " << result.get() << std::endl;return 0;
}

編譯命令

g++ -std=c++11 -O2 -pthread thread_pool.cpp -o thread_pool

輸出示例

Task Result: Task 3Task 1Task 20 executedexecuted
Task 5 executed
Task 40 executed
Task 7Task 6 executedexecutedexecutedexecutedResult: 1
Result: 4
Result: 9
Result: 16
Result: 25
Result: 36
Result: 49

結論:線程池復用線程,減少頻繁創建銷毀的開銷。


示例3:使用無鎖隊列提升并發

#include <iostream>
#include <atomic>
#include <thread>template<typename T>
class LockFreeQueue {
public:struct Node {T data;Node* next;Node(const T& data) : data(data), next(nullptr) {}};LockFreeQueue() : head(new Node(T())), tail(head.load()) {}void push(const T& data) {Node* newNode = new Node(data);Node* prevTail = tail.exchange(newNode);prevTail->next = newNode;}bool pop(T& result) {Node* oldHead = head.load();if (oldHead == tail.load()) return false;head.store(oldHead->next);result = oldHead->next->data;delete oldHead;return true;}private:std::atomic<Node*> head;std::atomic<Node*> tail;
};int main() {LockFreeQueue<int> queue;// 生產者線程std::thread producer([&] {for (int i = 0; i < 10; ++i) {queue.push(i);std::this_thread::sleep_for(std::chrono::milliseconds(10));}});// 消費者線程std::thread consumer([&] {int value;while (true) {if (queue.pop(value)) {std::cout << "Consumed: " << value << std::endl;if (value == 9) break;}}});producer.join();consumer.join();return 0;
}

編譯命令

g++ -std=c++11 -O2 -pthread lockfree_queue.cpp -o lockfree_queue

輸出示例

Consumed: 0
Consumed: 1
Consumed: 2
Consumed: 3
Consumed: 4
Consumed: 5
Consumed: 6
Consumed: 7
Consumed: 8
Consumed: 9

結論:無鎖隊列減少鎖爭用,適合高并發場景。


三、關鍵優化策略總結

  1. 減少鎖競爭

    • 縮小臨界區范圍。
    • 使用讀寫鎖(std::shared_mutex)區分讀寫操作。
  2. 利用原子操作

    • 簡單計數器優先用 std::atomic
    • 適當選擇內存序(如 memory_order_relaxed)。
  3. 異步與線程池

    • 避免頻繁創建線程,使用 std::async或自定義線程池。
  4. 無鎖數據結構

    • 在CAS(Compare-And-Swap)安全時使用,但需注意ABA問題。
  5. 緩存友好設計

    • 避免false sharing(用 alignas對齊或填充字節)。

四、性能測試方法論

  1. 基準測試

    • 使用 std::chrono精確測量代碼段耗時。
    • 對比不同實現的吞吐量(如ops/sec)。
  2. 壓力測試

    • 模擬高并發場景,觀察資源競爭情況。
  3. 工具輔助

    • Valgrind檢測內存問題。
    • perf分析CPU緩存命中率。

多選題

  1. 下列哪些情況可能導致數據競爭?
    A. 多個線程同時讀取同一變量
    B. 一個線程寫,另一個線程讀同一變量
    C. 使用互斥量保護共享變量
    D. 使用原子變量操作

  2. 關于std::asyncstd::thread的選擇,正確的說法是?
    A. std::async默認啟動策略是延遲執行
    B. std::thread更適合需要直接控制線程生命周期的場景
    C. std::async會自動管理線程池
    D. std::thread無法返回計算結果

  3. 以下哪些操作可能引發死鎖?
    A. 在持有鎖時調用外部未知代碼
    B. 對多個互斥量使用固定順序加鎖
    C. 遞歸互斥量(std::recursive_mutex)的嵌套加鎖
    D. 未及時釋放條件變量關聯的鎖

  4. 關于原子操作的內存順序,正確的說法是?
    A. memory_order_relaxed不保證操作順序
    B. memory_order_seq_cst會降低性能但保證全局順序
    C. memory_order_acquire僅保證讀操作的可見性
    D. 原子操作必須與volatile關鍵字結合使用

  5. 優化同步的合理手段包括:
    A. 將大臨界區拆分為多個小臨界區
    B. 使用無鎖數據結構替代互斥量
    C. 通過std::future傳遞計算結果
    D. 在單核系統上使用忙等待(busy-wait)

  6. 關于條件變量的正確使用方式:
    A. 必須在循環中檢查謂詞
    B. notify_one()notify_all()更高效
    C. 可以脫離互斥量單獨使用
    D. 虛假喚醒(spurious wakeup)是必須處理的

  7. 以下哪些是"鎖護送"(Lock Convoy)的表現?
    A. 多個線程頻繁爭搶同一互斥量
    B. 線程因鎖競爭頻繁切換上下文
    C. 互斥量的持有時間極短
    D. 使用讀寫鎖(std::shared_mutex

  8. 減少共享數據競爭的方法包括:
    A. 使用線程局部存儲(TLS)
    B. 復制數據到各線程獨立處理
    C. 通過消息隊列傳遞數據
    D. 增加互斥量的數量

  9. 關于std::promisestd::future的正確說法是:
    A. std::promise只能設置一次值
    B. std::futureget()會阻塞直到結果就緒
    C. 多個線程可以共享同一個std::future對象
    D. std::async返回的std::future可能延遲執行

  10. 關于原子變量與互斥量的對比,正確的說法是:
    A. 原子變量適用于簡單數據類型
    B. 互斥量能保護復雜操作序列
    C. 原子變量的fetch_add是原子的
    D. 互斥量比原子變量性能更好


設計題

  1. 實現一個線程安全的無鎖(lock-free)隊列
    要求:
  • 使用原子操作實現pushpop
  • 處理ABA問題
  • 提供測試代碼驗證并發操作正確性
  1. 設計一個生產者-消費者模型
    要求:
  • 使用std::condition_variablestd::mutex
  • 隊列長度限制為固定大小
  • 支持多生產者和多消費者
  • 提供測試代碼模擬并發場景
  1. 實現基于std::async的并行任務執行器
    要求:
  • 支持提交任意可調用對象
  • 自動回收已完成的任務資源
  • 限制最大并發線程數為CPU核心數
  • 測試代碼展示并行加速效果
  1. 優化高競爭場景下的計數器
    要求:
  • 使用線程局部存儲(TLS)分散寫操作
  • 定期合并局部計數到全局變量
  • 對比普通原子計數器與優化版本的性能差異
  • 提供測試代碼和性能統計輸出

5 實現一個讀寫鎖(Read-Write Lock)
要求:

  • 支持多個讀者或單個寫者
  • 避免寫者饑餓(writer starvation)
  • 基于std::mutexstd::condition_variable實現
  • 測試代碼驗證讀寫操作的互斥性

答案與詳解


多選題答案:
  1. B
    解析:數據競爭的條件是至少一個線程寫且無同步措施。A只有讀不沖突,C/D有同步機制。

  2. B, D
    解析std::async默認策略非延遲(C++11起為std::launch::async|deferred),B正確,D因std::thread無直接返回值機制正確。

  3. A, C
    解析:A可能導致回調中再次加鎖;C遞歸鎖允許同一線程重復加鎖但需對應解鎖次數,誤用仍可能死鎖。

  4. A, B
    解析memory_order_relaxed無順序保證,B正確,C中acquire保證后續讀的可見性,D錯誤(原子操作無需volatile)。

  5. A, B, C
    解析:D在單核忙等待浪費CPU,其余均為有效優化手段。

  6. A, B, D
    解析:C錯誤,條件變量必須與互斥量配合使用。

  7. A, B
    解析:鎖護送表現為頻繁爭搶導致線程切換,C短持有時間反而減少競爭,D無關。

  8. A, B, C
    解析:D增加鎖數量可能加劇競爭,其余均為減少競爭的有效方法。

  9. A, B, D
    解析:C錯誤,std::future不可多線程同時調用get()

  10. A, B, C
    解析:D錯誤,互斥量在低競爭時性能可能更差。


設計題答案示例

  1. 無鎖隊列實現(部分代碼)
#include <atomic>
#include <memory>template<typename T>
class LockFreeQueue {
private:struct Node {std::shared_ptr<T> data;std::atomic<Node*> next;Node() : next(nullptr) {}};std::atomic<Node*> head;std::atomic<Node*> tail;public:LockFreeQueue() : head(new Node), tail(head.load()) {}void push(T value) {Node* new_node = new Node;new_node->data = std::make_shared<T>(std::move(value));Node* old_tail = tail.load();while (!old_tail->next.compare_exchange_weak(nullptr, new_node)) {old_tail = tail.load();}tail.compare_exchange_weak(old_tail, new_node);}std::shared_ptr<T> pop() {Node* old_head = head.load();while (old_head != tail.load()) {if (head.compare_exchange_weak(old_head, old_head->next)) {std::shared_ptr<T> res = old_head->next->data;delete old_head;return res;}}return nullptr;}
};// 測試代碼
int main() {LockFreeQueue<int> queue;queue.push(42);auto val = queue.pop();if (val && *val == 42) {std::cout << "Test passed!\n";}return 0;
}
  1. 生產者-消費者模型
#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <iostream>template<typename T>
class SafeQueue {
private:std::queue<T> queue;std::mutex mtx;std::condition_variable cv;size_t max_size;public:SafeQueue(size_t size) : max_size(size) {}void push(T item) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [this] { return queue.size() < max_size; });queue.push(std::move(item));cv.notify_all();}T pop() {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [this] { return !queue.empty(); });T val = std::move(queue.front());queue.pop();cv.notify_all();return val;}
};// 測試代碼
int main() {SafeQueue<int> q(10);std::thread producer([&q] {for (int i = 0; i < 10; ++i) {q.push(i);}});std::thread consumer([&q] {for (int i = 0; i < 10; ++i) {int val = q.pop();std::cout << "Got: " << val << '\n';}});producer.join();consumer.join();return 0;
}

其他設計題目的答案, 稍后補充

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/73571.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/73571.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/73571.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[C++面試] RAII資源獲取即初始化(重點)

一、入門 1、什么是 RAII&#xff1f; RAII&#xff08;Resource Acquisition Is Initialization&#xff0c;資源獲取即初始化&#xff09;是 C 的核心編程范式&#xff0c;核心思想是 ?將資源的生命周期與對象的生命周期綁定&#xff1a; ?資源獲取&#xff1a;在對象構造…

Unity粒子系統

目錄 一、界面參數介紹1.主模塊2.Emission 模塊3.Shape 模塊4.Velocity over Lifetime 模塊5.Noise 模塊6.Limit Velocity Over Lifetime 模塊7.Inherit Velocity 模塊8.Force Over Lifetime 模塊9.Color Over Lifetime 模塊10.Color By Speed 模塊11.Size over Lifetime 模塊1…

Docker-清理容器空間prune

docker system prune -a 是一個非常有用的命令&#xff0c;用于清理 Docker 系統中未使用的資源&#xff0c;包括停止的容器、未使用的網絡、卷以及未被任何容器引用的鏡像&#xff08;懸空鏡像和所有未使用的鏡像&#xff09;。以下是關于該命令的詳細說明&#xff1a; 命令格…

LabVIEW遠程控制通訊接口

abVIEW提供了多種遠程控制與通訊接口&#xff0c;適用于不同場景下的設備交互、數據傳輸和系統集成。這些接口涵蓋從基礎的網絡協議&#xff08;如TCP/IP、UDP&#xff09;到專用技術&#xff08;如DataSocket、遠程面板&#xff09;&#xff0c;以及工業標準協議&#xff08;如…

LeetCode hot 100—尋找重復數

題目 給定一個包含 n 1 個整數的數組 nums &#xff0c;其數字都在 [1, n] 范圍內&#xff08;包括 1 和 n&#xff09;&#xff0c;可知至少存在一個重復的整數。 假設 nums 只有 一個重復的整數 &#xff0c;返回 這個重復的數 。 你設計的解決方案必須 不修改 數組 nums…

linux - centos7 部署 redis6.0.5

事先說明 本篇文章只解決在部署redis中出現的問題&#xff0c;并沒有部署redis的全過程&#xff0c;詳細部署過程可以參考Linux安裝部署Redis(超級詳細) - 長沙大鵬 - 博客園 執行 make 命令時報錯 原因&#xff1a;是因為gcc版本太低 升級gcc版本時 出現沒有可用軟件包 devt…

31天Python入門——第15天:日志記錄

你好&#xff0c;我是安然無虞。 文章目錄 日志記錄python的日志記錄模塊創建日志處理程序并配置輸出格式將日志內容輸出到控制臺將日志寫入到文件 logging更簡單的一種使用方式 日志記錄 日志記錄是一種重要的應用程序開發和維護技術, 它用于記錄應用程序運行時的關鍵信息和…

AI Agent開發大全第八課-Stable Diffusion 3的本地安裝全步驟

前言 就像我們前面幾課所述,本系列是一門體系化的教學,它不像網上很多個別存在的單篇博客走“吃快餐”模式,而是從扎實的基礎來帶領大家一步步邁向AI開發高手。所以我們的AI課程設置是相當全面的,除了有牢固的基礎知識外還有外面互聯網上也搜不到的生產級實戰。 前面講過…

用selenium+ChromeDriver豆瓣電影 肖申克的救贖 短評爬取(pycharm 爬蟲)

一、豆瓣電影 肖申克的救贖 短評urlhttps://movie.douban.com/subject/1292052/comments 二、基本知識點講解 1. Selenium 的基本使用 Selenium 是一個用于自動化瀏覽器操作的庫&#xff0c;常用于網頁測試和爬蟲。代碼中使用了以下 Selenium 的核心功能&#xff1a; webdriv…

開源在線客服系統源碼-前端源碼加載邏輯

客服源碼是使用Golang(又稱Go)開發的&#xff0c;Go是Google公司開發的一種靜態強類型、編譯型、并發型&#xff0c;并具有垃圾回收功能的編程語言。Go 天生支持并發。好處太多就不多說了。 全源碼客服系統用戶&#xff0c;想要針對自己的業務&#xff0c;進行二次開發&#xf…

Oracle數據庫服務器地址變更與監聽配置修改完整指南

一、前言 在企業IT運維中&#xff0c;Oracle數據庫服務器地址變更是常見的運維操作。本文將詳細介紹如何安全、高效地完成Oracle數據庫服務器地址變更及相關的監聽配置修改工作&#xff0c;確保數據庫服務在遷移后能夠正常運行。 二、準備工作 1. 環境檢查 確認新舊服務器I…

g對象在flask中主要是用來實現什么

在Flask中&#xff0c;g對象&#xff08;全稱flask.g&#xff09;是一個線程局部&#xff08;thread-local&#xff09;的臨時存儲對象&#xff0c;主要用于在單個請求的上下文&#xff08;request context&#xff09;中共享數據。它的核心作用是為同一請求的不同處理階段&…

工具介紹《WireShark》

Wireshark 過濾命令中符號含義詳解 一、比較運算符 Wireshark 支持兩種比較運算符語法&#xff1a;英文縮寫&#xff08;如 eq&#xff09;和 C語言風格符號&#xff08;如 &#xff09;&#xff0c;兩者功能等價。 符號&#xff08;英文縮寫&#xff09;C語言風格符號含義示…

JavaScrip-模版字符串的詳解

1.模版字符串的詳解 1.1 模版字符串的使用方法 在ES6之前&#xff0c;如果我們想要將字符串和一些動態的變量&#xff08;標識符&#xff09;拼接到一起&#xff0c;是非常丑陋的&#xff08;ugly) ES6允許我們使用模版字符串來嵌入變量或者表達式來進行拼接 首先&#xff0c;…

STM32C011 進入停止模式和待機模式

對于STM32C011J4M3微控制器&#xff0c;你可以使用HAL庫來實現進入停止模式&#xff08;Stop Mode&#xff09;和待機模式&#xff08;Standby Mode&#xff09;。下面是進入停止模式和待機模式的示例代碼&#xff1a; 進入停止模式代碼示例&#xff1a; #include "stm3…

海康設備http監聽接收報警事件數據

http監聽接收報警事件數據 海康獲取設備報警事件數據兩種方式&#xff1a; 1、sdk 布防監聽報警事件數據&#xff08;前面文章有示例&#xff09; 2、http監聽接收報警事件數據 http監聽接收報警事件數據&#xff0c;服務端可以使用netty通過端口來監聽獲取事件數據。 WEB 端…

FastAPI 全面指南:功能解析與應用場景實踐

FastAPI 全面指南&#xff1a;功能解析與應用場景實踐 FastAPI 是一個現代、快速&#xff08;高性能&#xff09;的 Python Web 框架&#xff0c;用于構建 API。它基于標準 Python 類型提示&#xff0c;使用 Starlette 和 Pydantic 構建&#xff0c;提供了極高的性能并簡化了開…

【STM32】編寫程序控制開發板的RGB LED燈

目錄 1、原理圖2、文件結構3、使用寄存器模式點亮3.1、什么是寄存器3.2、寄存器開發的本質3.3、寄存器開發步驟3.4、主要源碼3.4.1、main.c3.4.2、drv_gpio.h3.4.3、drv_gpio.c3.4.4、使用BSRR和BRR影子寄存器優化drv_gpio.c3.4.5、效果演示 4、使用標準庫模式點亮4.1、使用標準…

MyBatis-Plus 的加載及初始化

在 Spring Boot 啟動過程中&#xff0c;MyBatis-Plus 的加載和初始化涉及多個階段的工作。這些工作包括 MyBatis-Plus 自身的配置解析、Mapper 接口的掃描與注冊、SQL 語句的動態注入以及底層 MyBatis 的初始化等。以下是對整個過程的詳細分析&#xff1a; 1. Spring Boot 啟動…

SpringBoot中安全的設置阿里云日志SLS的accessKey

眾所周知,阿里云的服務都是基于accesskeyId和accesskeySecret來進行身份鑒權的,但唯獨日志因為需要寫入到.xml文件里對于accesskeyId和accesskeySecret需要進行一定程度的改進,尤其是使用了jasypt進行加密的參數傳遞進去logback.xml更是會遇到需要對參數進行解密的問題,而官網只…