多線程開發場景基本是每一個C++開發工程師無法避免的場景,今天就帶大家從零基礎入門C++多線程編程,掌握其中的基礎用法、鎖管理工具和條件變量的內容
多線程的認識
多線程就是一個程序內部運行多個任務,每個任務就是一個線程,充分利用CPU的資源,提高效率的技術
在多個線程中,作為程序入口的線程稱為主線程,由主線程創建負責獨立執行細分任務的線程稱為子線程
實現依賴
在C++中想使用多線程技術,就要引入頭文件<thread>
#include <thread>
線程創建基本流程
首先要定義線程要執行的任務函數,然後要通過std::thread實例化線程對象並且綁定該任務函數,最後調用join()函數或者detach()函數
其中join()來阻塞主線程,等待子線程執行完畢;detach()來將主線程和子線程分離
代碼示例如下
#include <iostream>
#include <thread>
// 子線程任務函數
void task(int num) {
cout << "子線程執行任務,傳入參數:" << num << endl;
}
int main() {
// 實例化thread對象並綁定任務,傳入參數5
std::thread t(task, 5);
// 阻塞主線程,等待子線程執行完成
t.join();
std::cout << "主線程繼續執行" << std::endl;
return 0;
}
為什麼要調用join(或detach()函數呢,如果不調用線程在對象析構時會觸發程序異常終止
互斥鎖
當多個線程訪問同一塊共享內存時,會出現數據讀寫混亂的情況,這就是競態條件,為了解決這個問題,可以採用互斥鎖std::mutex
std::mutex的核心接口是\
lock(),unlock(),try_lock()
lock()給互斥鎖加鎖,若鎖已被其他線程持有,則當前線程會阻塞,直到獲取到鎖,必須確保只有持unlock()是釋放鎖,確保只有持有鎖的線程才能調用,try_lock()是嘗試加鎖,成功返回true,失敗返回false,不會阻塞當前線程
代碼示例
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 定義全局互斥鎖
int shared_counter = 0; // 共享資源
// 子線程任務:對共享計數器進行累加
void increment_counter() {
for (int i = 0; i < 1000; i++) {
mtx.lock(); // 加鎖,進入臨界區
shared_counter++;
mtx.unlock(); // 解鎖,退出臨界區
}
}
int main() {
std::thread t1(increment_counter);
std::thread t2(increment_counter);
t1.join();
t2.join();
std::cout << "最終計數器值:" << shared_counter << std::endl; // 預期輸出2000
return 0;
}
互斥鎖雖然可以解決競態條件問題,但需要需手動調用lock()和unlock(),若臨界區代碼拋出異常,會導致unlock()無法執行,進而引發死鎖,而且多鎖場景下,若加鎖順序不一致,極易出現死鎖問題,操作難度太大,因而就有了讓互斥鎖更安全靈活的鎖管理工具
鎖管理工具
鎖管理工具主要有三種:分別是守衞鎖,唯一鎖,作用域鎖
std::lock_guard(守衞鎖)
std::lock_guard是最輕量化的RAII 鎖管理工具,構造時自動調用lock()加鎖,析構時自動調用unlock()解鎖
適用於簡單的臨界區資源保護,無需手動控制加解鎖時機,以下是代碼示例:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int shared_num = 0;
void add_num() {
// 構造lock_guard時自動加鎖
std::lock_guard<std::mutex> lock(mtx);
shared_num++;
std::cout << "當前共享變量值:" << shared_num << std::endl;
// 函數結束,lock_guard析構自動解鎖,即使中途拋異常也能正常解鎖
}
int main() {
std::thread t1(add_num);
std::thread t2(add_num);
t1.join();
t2.join();
return 0;
}
std::unique_lock(唯一鎖)
std::unique_lock是最靈活的 RAII 鎖管理工具,支持手動加解鎖、超時等待、延遲加鎖等操作,並且可配合條件變量使用
適用於複雜的鎖控制場景,需要靈活調整加解鎖時機,代碼示例如下:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int count = 0;
void task() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 延遲加鎖,構造時不自動加鎖
// 手動加鎖
lock.lock();
count += 5;
std::cout << "count更新為:" << count << std::endl;
// 手動解鎖
lock.unlock();
// 可再次加鎖
lock.lock();
count += 3;
std::cout << "count最終值:" << count << std::endl;
}
int main() {
std::thread t(task);
t.join();
return 0;
}
std::scoped_lock(作用域鎖)
std::scoped_lock是專為多鎖場景設計的RAII管理工具,能自動對傳入的多個互斥鎖排序加鎖,從根源杜絕多鎖死鎖問題
適用於需要同時獲取多個互斥鎖的場景,代碼示例如下:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx1, mtx2;
int data1 = 0, data2 = 0;
void update_data() {
// 自動對mtx1、mtx2排序加鎖,避免死鎖
std::scoped_lock lock(mtx1, mtx2);
data1++;
data2++;
std::cout << "data1=" << data1 << ",data2=" << data2 << std::endl;
}
int main() {
std::thread t1(update_data);
std::thread t2(update_data);
t1.join();
t2.join();
return 0;
}
條件變量
std::condition_variable(條件變量)是 C++ 多線程中實現線程阻塞等待的核心工具,它允許線程阻塞至特定條件滿足後再被喚醒,避免了 “忙等” 帶來的 CPU 資源浪費
核心特性
阻塞等待:線程調用wait()後會釋放持有的互斥鎖,進入阻塞狀態,直到被其他線程喚醒
喚醒機制:通過notify_one()喚醒一個等待線程,或notify_all()喚醒所有等待線程
綁定互斥鎖:必須配合std::unique_lock<std::mutex>使用,無法直接搭配其他鎖
虛假喚醒:即使未被主動喚醒,等待線程也可能被喚醒,因此需在循環中檢查條件是否真的滿足
適用於生產者 - 消費者模型:隊列滿或者空時讓對應線程等待,線程池,多線程同步等待某個事件觸發和超時等待場景等場景,代碼示例如下:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::queue<int> task_queue;
std::mutex mtx;
std::condition_variable cv;
// 生產者:往隊列中添加任務
void producer() {
for (int i = 1; i <= 5; i++) {
std::unique_lock<std::mutex> lock(mtx);
task_queue.push(i);
std::cout << "生產者生產任務:" << i << std::endl;
lock.unlock();
// 喚醒一個消費者線程
cv.notify_one();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
// 消費者:從隊列中取任務執行
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
// 循環檢查條件,避免虛假喚醒
cv.wait(lock, []{ return !task_queue.empty(); });
int task = task_queue.front();
task_queue.pop();
std::cout << "消費者執行任務:" << task << std::endl;
lock.unlock();
if (task == 5) break; // 任務執行完畢退出
}
}
int main() {
std::thread prod(producer);
std::thread cons(consumer);
prod.join();
cons.join();
return 0;
}
總結
多線程是通過在程序中創建多個線程,並且結合互斥鎖以及鎖管理工具和條件變量來實現CPU利用效率的提高,在短時間內跑完多個任務的技術,是我們每個C++程序員繞不開的技術
覺得文章對您有幫助的話可以點贊關注,我將會持續分享高質量的內容~