第 18.19 節
併發編程
0瀏覽次數0訪問次數--跳出率--平均停留
本節解決什麼問題
現代計算機通常有多個 CPU 核心。一個程序如果只在一個線程裡順序執行,就無法讓多個耗時任務同時推進。典型場景包括:
- 一個線程保持界面響應,另一個線程處理後臺任務。
- 同時等待多個網絡請求、定時器或外設事件。
- 把可拆分的計算任務分給多個線程。
併發編程的難點不在於"開線程"本身,而在於生命週期和共享數據。只要兩個線程同時讀寫同一份數據,就必須認真處理同步問題。
學習路線
本節拆成幾個文件級子章節來學:
std::thread:創建線程,並用join()等待結束。std::mutex和std::lock_guard:保護共享數據。std::atomic:簡單計數器的無鎖線程安全操作。std::condition_variable:一個線程等待另一個線程發通知。
如果只啟動一個任務,同步和併發的結果可能看起來一樣。差異通常要在"兩個或更多任務同時等待或同時計算"時才明顯。這和 Boost.Asio 中一個定時器看不出異步優勢、兩個定時器才看出區別是同一個道理。
C++ 標準版本
C++11 引入了標準線程庫。C++14、C++17、C++20 又增加了 std::shared_mutex、std::jthread、std::latch、std::barrier 等工具。
本節主要使用 C++11 就有的基礎工具。編譯時通常需要加線程選項,例如:
g++ demo.cpp -std=c++17 -pthread
常用工具
| 工具 | 作用 | 常見頭文件 |
|---|---|---|
std::thread | 創建和管理線程 | <thread> |
std::this_thread::sleep_for | 當前線程休眠一段時間 | <thread>、<chrono> |
std::mutex | 互斥鎖,保護共享數據 | <mutex> |
std::lock_guard | RAII 加鎖,作用域結束自動解鎖 | <mutex> |
std::unique_lock | 更靈活的 RAII 鎖,常配合條件變量 | <mutex> |
std::atomic | 原子變量,適合簡單計數 | <atomic> |
std::condition_variable | 等待和通知 | <condition_variable> |
併發和並行
| 概念 | 含義 | 例子 |
|---|---|---|
| 併發 | 多個任務在同一段時間內推進 | 一個程序同時等待網絡、定時器和用戶輸入 |
| 並行 | 多個任務真的同時運行 | 多核 CPU 上多個線程同時計算 |
初學時先抓住工程直覺:多個任務都要等待外設、網絡、定時器或耗時計算時,讓它們同時推進,程序整體會更快或響應更好。
使用建議
- 先保證正確,再考慮性能。
- 能不共享數據就不共享數據,每個線程處理自己的數據最簡單。
- 共享數據優先用
mutex保護,簡單計數才考慮atomic。 - 鎖的作用域儘量小,避免長時間持鎖。
- 初學階段少用
detach(),優先用join()明確等待線程結束。 - C++20 項目可以瞭解
std::jthread,它析構時會自動請求停止並 join,比std::thread更安全。
工程拓展
在 ROS2 中,多線程回調執行器、回調組和共享狀態保護都會涉及線程與鎖。在 Boost.Asio 中,多個異步任務可能運行在同一個 io_context 或線程池裡,也要考慮回調之間的共享數據。學好 C++ 標準庫併發工具,再看這些工程庫會輕鬆很多。