c++ condition_variable的wait 语法糖
最近在复盘之前用到的线程同步的一些知识点,话不多说,先看个例子吧:
摘自: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; });