引:由上一篇mutex的介紹,基本可知通過設置mutex鎖可以解決不同線程修改共享變量的線程安全問題。然而,有些時候並不止要求不同線程間訪存的數據安全,而且需要各線程按照某一順序進行訪存或執行(條件變量是併發程序設計中的一種控制結構),這種情況就需要通過設置condition variable(條件變量)實現。條件變量的一般用法是:線程 A 等待某個條件並掛起,直到線程 B 設置了這個條件,並通知條件變量,然後線程 A 被喚醒。經典的 「生產者-消費者」 問題就可以用條件變量來解決。
note1 :條件變量的等待函數wait(lck) 需要與一個互斥鎖搭配使用 ,要明白搭配使用的機理,否則理解上會造成混亂:在調用wait時:
首先該線程需要獲取到保護v的鎖 進入臨界取,因爲 v 應該是多個線程可以訪問的。
在wait函數執行如下操作:
wait函數內部首先 mu.unlock() 釋放鎖 ;
然後進入等待;
如果被喚醒,則調用mu.lock() 再次獲取鎖。
note2 :condition_variable類更多成員函數含義和用法可見 .
實例
#include<iostream>
#include<string>
#include<thread>
#include<mutex>
#include<condition_variable>
std::mutex mutex;
std::condition_variable cv;
std::string data;
bool ready = false;//條件
bool processed = false;//條件
void Worker()
{
std::unique_lock<std::mutex> lock(mutex);
//等待主線程發送數據
cv.wait(lock, []() {return ready; });
// 等待後,繼續擁有鎖。
std::cout << "工作線程正在處理數據" << std::endl;
// 睡眠一秒以模擬數據處理。
std::this_thread::sleep_for(std::chrono::seconds(1));
data += "已處理";
processed = true;
std::cout << "工作線程通知數據已經處理完畢" << std::endl;
// 通知前,手動解鎖以防正在等待的線程被喚醒後又立即被阻塞。
lock.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(Worker);
{
std::lock_guard<std::mutex> lock(mutex);
std::cout << "主線程正在準備數據..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
data = "樣本數據";
ready = true;
std::cout << "主線程通知數據已經準備完畢" << std::endl;
}
cv.notify_one();
{
std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock, [] {return processed; });
}
std::cout << "回到主線程, 數據 = " << data << std::endl;
worker.join();
system("pause");
return 0;
}
代碼說明:
與條件變量搭配使用的「鎖」,必須是 unique_lock,不能用 lock_guard(unique_lock鎖機制更加靈活,可以再需要的時候進行lock或者unlock調用,而lock_guard不行),通過上邊wait函數的原理即可知爲什麼只能是unique_lock而不能是lock_guard。
條件變量被通知後,掛起的線程就被喚醒,但是喚醒也有可能是假喚醒,或者是因爲超時等異常情況,所以被喚醒的線程仍要檢查條件是否滿足,所以 wait 是放在條件循環裏面。cv.wait(lock, [] { return ready; }); 相當於:while (!ready) { cv.wait(lock); }。(condition_variable各成員函數詳細見 )
請注意理解main函數中兩個大括號{}使用的意義(變量作用域與對象析構)