多線程(標準線程庫?<thread>
)
創建線程
#include <iostream>
#include <thread>void hello() {std::cout << "Hello from thread!\n";
}int main() {// 創建線程并執行 hello() std::thread t(hello); //線程對象,傳入可調用對象(函數、Lambda、函數對象)t.join(); // 等待線程結束 阻塞主線程,直到子線程完成。
//t.detach():分離線程(線程獨立運行,主線程不等待)。
// 僅在明確不需要管理線程生命周期時使用 detach(),并確保資源安全。return 0;
}
傳遞參數(和平常函數調用不同 注意看):
void print_sum(int a, int b) {std::cout << a + b << "\n";
}int main() {std::thread t(print_sum, 10, 20); // 傳遞參數t.join();
}
Lambda 表達式 線程:
std::thread t([] {std::cout << "Lambda thread\n";
});
t.join();
線程同步:
互斥鎖(Mutex):防止多個線程訪問 共享數據:
#include <mutex>std::mutex mtx;
int shared_data = 0;//原始手動加鎖
void increment() {mtx.lock(); // 加鎖:如果其他線程已鎖,這里會阻塞等待shared_data++; // 臨界區:唯一線程能執行的代碼mtx.unlock(); // 解鎖:允許其他線程進入
}
//風險:如果 shared_data++ 拋出異常,unlock() 可能不被執行,導致死鎖。void safe_increment() {std::lock_guard<std::mutex> lock(mtx); // 構造時自動加鎖shared_data++; // 臨界區
} // 析構時自動解鎖(即使發生異常)
//RAII(資源獲取即初始化):利用對象生命周期自動管理鎖,避免忘記解鎖。加鎖后的正確流程
線程A加鎖 → shared_data++(變為1)→ 解鎖線程B加鎖 → shared_data++(變為2)→ 解鎖
結果:shared_data = 2。
高階用法:
void flexible_increment() {std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 延遲加鎖// ...其他非臨界區代碼...lock.lock(); // 手動加鎖shared_data++;lock.unlock(); // 可手動提前解鎖
}=================================================//多個鎖時,按固定順序獲取:
std::mutex mtx1, mtx2;void safe_operation() {std::lock(mtx1, mtx2); // 同時加鎖(避免死鎖)std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);// 操作多個共享資源
}=============錯誤示范===============
int* get_data() {std::lock_guard<std::mutex> lock(mtx);return &shared_data; // ? 危險!鎖失效后仍可訪問
}注意事項
鎖粒度:鎖的范圍應盡量小(減少阻塞時間)。避免嵌套鎖:容易導致死鎖。不要返回鎖保護的指針/引用:會破壞封裝性。
線程之間通知機制 (條件變量)
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>std::mutex mtx;
std::condition_variable cv;
bool data_ready = false; // 條件變量依賴的共享狀態// 消費者線程(等待數據)
void consumer() {std::unique_lock<std::mutex> lock(mtx);std::cout << "消費者: 等待數據...\n";cv.wait(lock, [] { return data_ready; }); // 等待條件成立std::cout << "消費者: 收到數據,開始處理!\n";
}// 生產者線程(準備數據)
void producer() {std::this_thread::sleep_for(std::chrono::seconds(1)); // 模擬耗時操作{std::lock_guard<std::mutex> lock(mtx);data_ready = true; // 修改共享狀態std::cout << "生產者: 數據已準備!\n";}cv.notify_one(); // 通知等待的消費者線程
}int main() {std::thread t1(consumer); // 消費者線程(等待)std::thread t2(producer); // 生產者線程(通知)t1.join();t2.join();return 0;
}//輸出效果
消費者: 等待數據...
生產者: 數據已準備!
消費者: 收到數據,開始處理!關鍵點解析
條件變量 (std::condition_variable)用于線程間的條件同步,允許線程阻塞直到某個條件成立。必須與 std::mutex 和 一個共享狀態變量(如 bool data_ready)配合使用。
-------------------------------------------------------------------------
cv.wait(lock, predicate) 的工作原理步驟1:線程獲取鎖后檢查條件(predicate)。步驟2:若條件為 false,線程釋放鎖并進入阻塞狀態,等待通知。步驟3:當其他線程調用 notify_one() 時,線程被喚醒,重新獲取鎖并再次檢查條件。步驟4:若條件為 true,線程繼續執行;否則繼續等待。為什么需要 data_ready 變量?避免虛假喚醒:操作系統可能意外喚醒線程,因此需要顯式檢查條件。狀態同步:明確線程間的通信意圖(如“數據已準備好”)。鎖的作用域生產者:修改 data_ready 時必須加鎖(lock_guard)。消費者:wait() 會自動釋放鎖,喚醒后重新獲取鎖。
異步任務(得重點掌握):
std::async
異步執行函數,返回?
std::future
:#include <future>int compute() { return 42; }int main() {std::future<int> result = std::async(compute);std::cout << "Result: " << result.get() << "\n"; // 阻塞獲取結果 }std::launch::async:立即異步執行。std::launch::deferred:延遲到 get() 時執行。
=====================================================================
std::packaged_task
將函數包裝為可異步調用的任務:
std::packaged_task<int()> task([] { return 7 * 6; }); //異步包裝 std::future<int> result = task.get_future(); //future 用于稍后獲取異步結果。 std::thread t(std::move(task)); // 在線程中執行 t.join(); std::cout << "Result: " << result.get() << "\n";================================================== 通過 std::move:將 task 的所有權轉移給線程 t,避免拷貝。轉移后,原 task 對象變為 空狀態(不能再調用)
線程管理?
獲取硬件線程數:
std::thread::hardware_concurrency()
?返回的是?當前計算機硬件支持的線程并發數(通常等于邏輯CPU核心數)
線程不超過線程數時 效果最佳
unsigned cores = std::thread::hardware_concurrency();
std::cout << "CPU cores: " << cores << "\n";
//std::thread::hardware_concurrency() 返回的是
//當前計算機硬件支持的線程并發數(通常等于邏輯CPU核心數)4 核 4 線程 CPU → 輸出 44 核 8 線程 CPU → 輸出 8蘋果 M1 Max (10 核) → 輸出 10
線程局部儲存(每個線程獨享的變量TLS):
thread_local int counter = 0; // 每個線程有獨立副本
原子操作(無須鎖的安全操作)
為什么不需要鎖?
1.硬件支持
CPU 原子指令:現代 CPU 提供專門的指令(如 x86 的?
LOCK XADD
、ARM 的?LDREX/STREX
)確保單條指令完成“讀取-修改-寫入”操作,不會被線程切換打斷。緩存一致性協議:通過 MESI 等協議保證多核間對原子變量的可見性。
2. 編譯器與語言標準保障
編譯器屏障:
std::atomic
?操作會阻止編譯器重排序相關指令。內存順序控制:支持靈活的內存序(如?
memory_order_relaxed
、memory_order_seq_cst
),平衡性能與一致性需求。
#include <iostream>
#include <atomic>
#include <thread>std::atomic<int> counter(0);void increment(int n) {for (int i = 0; i < n; ++i) {counter++; // 原子自增}
}int main() {std::thread t1(increment, 100000);std::thread t2(increment, 100000);t1.join(); t2.join();std::cout << "Counter: " << counter << "\n"; // 保證輸出 200000return 0;
}
特性 | std::atomic | std::mutex |
---|---|---|
實現層級 | 硬件指令 + 編譯器優化 | 操作系統級鎖(可能涉及系統調用) |
粒度 | 單個變量操作 | 保護任意代碼塊 |
性能 | 極高(無鎖設計) | 較高(存在鎖爭用開銷) |
適用場景 | 簡單變量(int、bool、指針等) | 復雜邏輯或跨多個變量的操作 |
?
std::atomic
?的局限性
-
僅適用于標量類型:對結構體等復雜類型需自定義或使用鎖。
-
內存序選擇:錯誤的內存序可能導致意外行為(如?
memory_order_relaxed
?不保證順序)。
死鎖預防:
避免嵌套鎖:按固定順序加鎖。
使用?
std::lock
?同時鎖多個互斥量(前面互斥鎖有拓展):std::mutex mtx1, mtx2; std::lock(mtx1, mtx2); // 同時加鎖(避免死鎖) std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock); std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
線程池(運用第三方庫實現)廣泛應用于需要高并發處理短任務的場景(如HTTP服務器、并行計算等)
第三方庫(如?BS::thread_pool):
#include "thread_pool.hpp" // 引入線程池庫頭文件
BS::thread_pool pool; // 創建默認線程池(線程數=硬件并發數)
auto task = pool.submit([] { return 42; }); // 提交Lambda任務
std::cout << task.get() << "\n"; // 阻塞等待并獲取結果
BS::thread_pool | 線程池類,管理一組工作線程(通常數量=CPU核心數)。 |
pool.submit() | 提交任務(函數/Lambda)到線程池,返回?std::future ?對象。 |
task.get() | 阻塞調用線程,直到任務完成并返回結果(類似?std::future::get )。 |
?
工作流程
線程池初始化
創建時默認啟動?
std::thread::hardware_concurrency()
?個工作線程。線程空閑時會自動從任務隊列中取任務執行。
任務提交
submit
?將 Lambda?[] { return 42; }
?封裝為任務,放入隊列。返回的?
task
?是一個?std::future<int>
,用于后續獲取結果。結果獲取
task.get()
?會阻塞主線程,直到某個工作線程完成該任務。最終輸出?
42
。
對比原生?std::thread
特性 | BS::thread_pool | std::thread |
---|---|---|
線程管理 | 自動復用線程(避免頻繁創建/銷毀) | 需手動管理線程生命周期 |
任務隊列 | 支持批量提交任務 | 需自行實現任務隊列 |
開銷 | 低(線程復用) | 高(每次任務新建線程) |
適用場景 | 大量短任務 | 少量長任務 |
?拓展用法示例:
//批量提交任務
std::vector<std::future<int>> results;
for (int i = 0; i < 10; ++i) {results.push_back(pool.submit([i] { return i * i; }));
}
for (auto& r : results) {std::cout << r.get() << " "; // 輸出 0 1 4 9 16 25 36 49 64 81
}//獲取線程池信息
std::cout << "線程數: " << pool.get_thread_count() << "\n";//等待所有任務完成
pool.wait(); // 阻塞直到所有任務完成(不銷毀線程)
總結
功能 | 工具 | 頭文件 |
---|---|---|
線程創建 | std::thread | <thread> |
互斥鎖 | std::mutex ,?std::lock_guard | <mutex> |
條件變量 | std::condition_variable | <condition_variable> |
異步任務 | std::async ,?std::future | <future> |
原子操作 | std::atomic | <atomic> |
線程局部存儲 | thread_local | 語言內置 |