最近在復盤之前用到的線程同步的一些知識點,話不多說,先看個例子吧:
摘自:http://www.cplusplus.com/reference/condition_variable/condition_variable/wait/
// condition_variable::wait (with predicate) #include <iostream> // std::cout #include <thread> // std::thread, std::this_thread::yield #include <mutex> // std::mutex, std::unique_lock #include <condition_variable> // std::condition_variable std::mutex mtx; std::condition_variable cv; int cargo = 0; bool shipment_available() {return cargo!=0;} void consume (int n) { for (int i=0; i<n; ++i) { std::unique_lock<std::mutex> lck(mtx); cv.wait(lck,shipment_available); // consume: std::cout << cargo << '\n'; cargo=0; } } int main () { std::thread consumer_thread (consume,10); // produce 10 items when needed: for (int i=0; i<10; ++i) { while (shipment_available()) std::this_thread::yield(); std::unique_lock<std::mutex> lck(mtx); cargo = i+1; cv.notify_one(); } consumer_thread.join(); return 0; }
這里主要是想回顧一下std::condition_variable的用法,首先可以看到,它有默認構造函數。
然后是關鍵的wait方法,它有兩個版本,一個是無條件的,也是我之前用的版;另一個是帶謂詞的版本
unconditional (1) void wait (unique_lock<mutex>& lck); predicate (2) template <class Predicate> void wait (unique_lock<mutex>& lck, Predicate pred);
但是不管哪個版本,都需要一個unique_lock, 需要lock可以理解,畢竟兩個線程同步,condition variable只能起到通知的作用,那么為什么需要的是unique_lock呢,這是一個更加深入的問題,不是本文的重點,后面我再研究,第一層我們先foucus在用法和注意事項上面。
需要lock還有另外一個好處或者說原因,那就是進入wait的時候,可以釋放傳入的鎖,本線程被阻塞,並且會讓出CPU的占用。
cppreference上面是這么說的:
The execution of the current thread (which shall have locked lck's mutex) is blocked until notified.
At the moment of blocking the thread, the function automatically callslck.unlock()
, allowing other locked threads to continue.
當wait等到了通知信號的時候:
wait會重新持鎖,就像一開始進入wait函數時候那樣
Once notified (explicitly, by some other thread), the function unblocks and calls
lck.lock()
, leaving lck in the same state as when the function was called. Then the function returns (notice that this last mutex locking may block again the thread before returning).
這里提到了一個“驚群效應”
Generally, the function is notified to wake up by a call in another thread either to membernotify_oneor to membernotify_all. But certain implementations may produce spurious wake-up calls without any of these functions being called. Therefore, users of this function shall ensure their condition for resumption is met.
舉個例子,假設有10個貨船線程實際上都在cv上面等待有貨物的通知,有了貨物就去消費,而且是直接把cargo消費完的那種。那么生產者線程生產了貨物之后,發出了一個notify_all的通知,假如消費者線程只接受通知,不檢查貨物數量,很有可能的問題就是,某個先被喚醒的立即拿到鎖把cargo消費完了,別的貨船只等到了通知,但是卻已經沒有了貨物。所以,wait提供了一個語法糖,加了一個謂詞對象,所謂的謂詞對象是:
A callable object or function that takes no arguments and returns a value that can be evaluated as a
bool
.
If pred is specified (2), the function only blocks if pred returns false
, and notifications can only unblock the thread when it becomes true
(which is specially useful to check against spurious wake-up calls). This version (2) behaves as if implemented as:
也就是說,一旦帶有pred的wait被notify的時候,它會去檢查謂詞對象的bool返回值是否是true, 如果是true才真正喚醒,否則繼續block.大概是這么個意思:
while (!pred()) wait(lck);
那么用lambda表達式改寫上述wait邏輯如下:
//lambda表達式完全體形態
cv.wait(lck, []()->bool{ return cargo != 0; });
//或者省略尾置返回類型
cv.wait(lck, [](){ return cargo != 0; });
//或者省略尾置返回類型+參數列表
//但是必須有捕獲列表和函數體
cv.wait(lck, []{ return cargo != 0; });