std::condition_variable是C++11新加入的用于多个线程之间同步的一种机制,头文件是
#include
#include
#include
#include
#include
生产者线程不断生产数据并放入g_deque中,10个消费者线程不断从g_deque中获取数据并删除。没有数据的时候消费者线程调用wait函数阻塞当前线程,当生产者数据放入g_deque之后调用notify_all函数唤醒所有等待的线程。因为是10个消费者线程共同竞争数据,所以最终只有一个线程得到数据,另外9个线程被唤醒之后发现g_deque为空继续调用wait函数阻塞自己,这就导致了一个虚假唤醒的概念:明明当前线程已经被唤醒了,却得不到需要的数据。
网络对于虚假唤醒的解释主要有两种:一种就是上面解释的由notify_all唤醒之后却得不到需要的数据,一种是有的系统会出于某种原因唤醒正在阻塞队列的线程,这时候消费者线程也是得不到需要的数据的(因为不是由生产者线程唤醒)。两种说法兼而有之,都没有错,更准确的说法应该是第一种,两种的说法可以看wiki解释:https://en.wikipedia.org/wiki/Spurious_wakeup。
A spurious wakeup happens when a thread wakes up from waiting on a condition variable that's been signaled, only to discover that the condition it was waiting for isn't satisfied
虚假唤醒的意思时,当一个正在等待条件变量的线程由于条件变量被触发而唤醒时,却发现它等待的条件(共享数据)没有满足(也就是没有共享数据)。
这个是比较直观正统的解释,但是这个wiki里面还有一句话:
To allow for implementation flexibility in dealing with error conditions and races inside the operating system, condition variables may also be allowed to return from a wait even if not signaled
为了给操作系统提供处理错误情况和(线程)竞争实现(更大的)灵活性,条件变量即使没有被触发,它也可能被允许返回。
这个就是第二种说法的由来,但是从实际的测试来看(程序运行一天一夜),这种情况在windows和Centos下不会发生,同样是上面wiki链接,关于虚假唤醒的第二种里面有一句话:
The Linux pthread implementation of condition variables guarantees it will not do that。
也就是说Linux线程里面关于条件变量的实现保证永远不会触发第二种虚假唤醒的情况,这也印证了我们的测试。
针对虚假唤醒的情况,解决办法就是在每次使用共享数据之前先判断一下是否为空,为空则继续等待
while (g_deque.empty())
{
g_cond.wait(lck);
}
这里不能使用if的判断方式,因为if只会执行一次,无法解决多次虚假唤醒的情况。
也可以使用lambda表达式来解决,不用while循环,只有在g_deque不为空的情况下才会返回true
g_cond.wait(lck, [] {return !g_deque.empty(); });