目錄<condition_variable>condition_variable類類方法生產者消費者模型 -- 阻塞隊列單條件變數版condition_variable_any模板類區別優缺點 <condition_variable> 條件變數是C++11提供的另外一種用於等待的同步機制,它能阻塞一 ...
目錄
<condition_variable>
條件變數是C++11提供的另外一種用於等待的同步機制,它能阻塞一個或多個線程,直到收到另外一個線程發出的通知或者超時時,才會喚醒當前阻塞的線程。條件變數需要和互斥量配合起來使用,C++11提供了兩種條件變數:
條件變數為什麼叫變數?
在電腦科學和併發編程中,條件變數是一種用於線程同步的機制,它們之所以被稱為“變數”,主要有以下幾個原因:
- 狀態表示:條件變數本質上是一個表示狀態的對象,這個狀態可以被其他線程檢查和修改。變數這個詞意味著它是一個可以存儲和表示某種狀態的實體。
- 動態性:條件變數的狀態是動態變化的。線程可以等待一個條件變數的某個狀態,然後在條件滿足時被喚醒。這種動態變化的特性使得它像一個普通的變數,可以在程式運行時不斷變化。
- 操作性:條件變數可以通過特定的操作來改變其狀態。通常,條件變數有兩個主要操作:等待(wait)和通知(signal或broadcast)。這些操作類似於對普通變數進行的讀寫操作,只不過這些操作影響的是線程的執行流。
- 命名約定:在許多編程語言和庫中,條件變數被設計為一種數據結構或對象,並且通常以變數的形式存在於代碼中。為了與其他同步機制(如互斥鎖、信號量等)區分開來,並保持命名的一致性,使用“變數”這個詞來描述它們。
綜上所述,條件變數被稱為“變數”主要是因為它們具有狀態表示的特性,可以通過操作改變狀態,併在程式中以變數的形式出現,從而幫助線程實現同步。
condition_variable類
condition_variable實現在<condition_variable>中,在VS2019中
類方法
- 線程等待(阻塞/休眠)函數
1. void wait (unique_lock<mutex>& lck); //,解鎖,進入休眠,等待喚醒
2. template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred);
如果線程被該函數阻塞,這個線程會釋放占有的互斥鎖的所有權,當阻塞解除之後這個線程會重新得到互斥鎖的所有權,繼續向下執行
-
condition_variable的wait()的lck參數無法直接使用互斥鎖,必須搭配std::unique_lock<>類使用
-
wait的第二重載方法中,Pred參數是一個模板參數,用於接收返回值為bool函數或函數對象/lambda表達式.
每次喚醒在wait隊列內休眠的線程時,線程都會檢查Rred的值,只有為真時才會繼續往下執行,否則繼續休眠
- Pred值為假,線程進入休眠,等待喚醒
- Pred值為真,線程繼續向下執行.
- 線程通知/喚醒函數 -- (notify:通知)
1. void notify_one() noexcept; //在wait隊列中喚醒一個
2. void notify_all() noexcept; //全部喚醒
- wait_for和wait_until
wait_for()函數,waitr_until()函數都和wait()的功能是一樣的,只不過多了一個阻塞時長,假設阻塞的線程沒有被其他線程喚醒,當阻塞時長用完之後,線程就會自動解除阻塞,繼續向下執行。
wait_for
a.
template <class Clock, class Duration>
cv_status wait_until (unique_lock<mutex>& lck,
const chrono::time_point<Clock,Duration>& abs_time);
b.
template <class Clock, class Duration, class Predicate>
bool wait_until (unique_lock<mutex>& lck,
const chrono::time_point<Clock,Duration>& abs_time, Predicate pred);
wait_until
a.
template <class Clock, class Duration>
cv_status wait_until (unique_lock<mutex>& lck,
const chrono::time_point<Clock,Duration>& abs_time);
b.
template <class Clock, class Duration, class Predicate>
bool wait_until (unique_lock<mutex>& lck,
const chrono::time_point<Clock,Duration>& abs_time, Predicate pred);
生產者消費者模型 -- 阻塞隊列
單條件變數版
運用:wait(lck), wait(lck,Pred), notify_all(), condition_variable
#include<iostream> //std::cout
#include<thread> //std::thread
#include<mutex> //std::mutex, std::unique_lock, std::lock_guard
#include<queue> //std::queue
#include<condition_variable> //std::condition_variable
#include<functional> //std::bind
//設計概要
/*
定長隊列+自動管理增刪
同步 == 獨占互斥鎖
- 生產者生產 == 增加 -- bool?生產成功返回true,放不下false --- (生產者一定知道自己已生產)生產:通知消費者
- 消費者消費 == 刪除 -- bool?消費成功返回true,沒有了false --- (同理) 消費:通知生產者
*/
//單條件變數版 -- 簡化邏輯
template<class T>
class BlockQueue {
public:
bool isEmpty() {
return _bqueue.empty();
}
bool isFull() {
return _bqueue.size() == _capacity;
}
//AddTask
void Push(const T& t) {
std::unique_lock<std::mutex> lck(_mtx);
while (isFull()) {
cv.wait(lck);
}
_bqueue.push(t);
std::cout << "模擬插入數據..." << t << "\n";
lck.unlock();
cv.notify_all();
}
//DelTask
void Pop() {
std::unique_lock<std::mutex>lck(_mtx);
cv.wait(lck, [this]() {
bool flag = isEmpty(); //為空時休眠
return !flag; //增加可讀性的策略
});
T t = _bqueue.front();
_bqueue.pop();
std::cout << "模擬處理數據..." << t << "\n";
lck.unlock();
cv.notify_all();
}
private:
std::queue<T> _bqueue;
std::mutex _mtx;
std::condition_variable cv; //必須搭配all
size_t _capacity = 5; //隊列定長大小
//std::condition_variable _not_full; //非滿時喚醒生產者
//std::condition_variable _not_empty; //非空時喚醒消費者
};
int main() {
BlockQueue<int> tq;
auto produce = std::bind(&BlockQueue<int>::Push, &tq, std::placeholders::_1);
auto consume = std::bind(&BlockQueue<int>::Pop, &tq);
std::thread t1[5];
std::thread t2[5];
for (int i = 0; i < 5; i++) {
t1[i] = std::thread(produce, i);
t2[i] = std::thread(consume);
}
for (int i = 0; i < 5; i++) {
t1[i].join();
t2[i].join();
}
return 0;
}
condition_variable_any模板類
condition_variable_any實現在<condition_variable>中,在VS2019中
區別
condition_variable_any與condition_variable的區別是
- condition_variable_any可以直接給wait()函數傳互斥鎖std::mutex、std::timed_mutex、std::recursive_mutex、std::recursive_timed_mutex四種.
- condition_variable在wait()時必須搭配unique_lock使用
優缺點
- condition_variable實現簡單,效率更高.缺點是只能搭配
std::mutex和unique_lock
使用 - condition_variable_any更加靈活,但是實現複雜,效率會低一些,僅僅是低一些,對於現代電腦,這點效率損失不成問題.