[C++11 多线程同步] --- 条件变量的那些坑条件变量信号丢失和条件变量虚假唤醒(spurious wakeup)
Posted Overboom
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[C++11 多线程同步] --- 条件变量的那些坑条件变量信号丢失和条件变量虚假唤醒(spurious wakeup)相关的知识,希望对你有一定的参考价值。
1 条件变量的信号丢失
1.1 条件变量的信号丢失场景重现
拿生产者和消费者模型举例,看一段示例代码:
#include <iostream>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <unistd.h>
std::mutex mutex;
std::condition_variable cv;
std::vector<int> vec;
void consumer()
sleep(1);
std::unique_lock<std::mutex> lock(mutex);
std::cout << "blocking on wait" << std::endl;
cv.wait(lock);
std::cout << "executed wait" << std::endl;
std::cout << "consume " << vec.size() << "\\n";
void produce()
std::unique_lock<std::mutex> lock(mutex);
vec.push_back(1);
cv.notify_all();
std::cout << "produce" << std::endl;
int main()
std::thread consumers, producers;
consumers = std::thread(consumer);
producers = std::thread(producer);
consumers.join();
producers.join();
return 0;
执行结果如下:
这里程序段本意是消费者线程阻塞,等待生产者生产数据后去通知消费者线程,这样消费者线程就可以拿到数据去消费。
条件变量的信号丢失原因分析:
在consumer线程中sleep 1s,让生产者线程先执行,并通过notify_all通知消费者线程;
此时消费者线程并没有执行到wait语句,也即消费者线程没有处于挂起状态,线程没有等待此条件变量上,通知信号就丢失了;
1s后消费者线程再执行wait处于等待状态时,生产者不会再触发notify,消费者线程会一直阻塞
1.2 条件变量的信号丢失问题解决
如何解决这个问题呢?可以附加一个判断条件,就可以解决这种信号丢失问题,下面给出代码:
#include <iostream>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <unistd.h>
std::mutex mutex;
std::condition_variable cv;
std::vector<int> vec;
void consumer()
sleep(1);
std::unique_lock<std::mutex> lock(mutex);
std::cout << "blocking on wait" << std::endl;
if (vec.empty)
cv.wait(lock);
std::cout << "executed wait" << std::endl;
std::cout << "consume " << vec.size() << "\\n";
void produce()
std::unique_lock<std::mutex> lock(mutex);
vec.push_back(1);
cv.notify_all();
std::cout << "produce" << std::endl;
int main()
std::thread consumers, producers;
consumers = std::thread(consumer);
producers = std::thread(producer);
consumers.join();
producers.join();
return 0;
执行结果如下:
通过增加判断条件可以解决信号丢失的问题。
但这里还有个地方需要注意,消费者线程处于wait阻塞状态时,即使没有调用notify,操作系统也会有一些概率会唤醒处于阻塞的线程,使其继续执行下去,这就是虚假唤醒问题,当出现了虚假唤醒后,消费者线程继续执行,还是没有可以消费的数据,出现了bug
2 条件变量的虚假唤醒解决方式
那怎么解决虚假唤醒的问题呢,可以在线程由阻塞状态被唤醒后继续判断附加条件,看是否满足唤醒的条件,如果满足则继续执行,如果不满足,则继续去等待,体现在代码中,即将if判断改为while循环判断,见代码:
#include <iostream>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <unistd.h>
std::mutex mutex;
std::condition_variable cv;
std::vector<int> vec;
void consumer()
sleep(1);
std::unique_lock<std::mutex> lock(mutex);
std::cout << "blocking on wait" << std::endl;
while (1)
cv.wait(lock);
std::cout << "executed wait" << std::endl;
std::cout << "consume " << vec.size() << "\\n";
void produce()
std::unique_lock<std::mutex> lock(mutex);
vec.push_back(1);
cv.notify_all();
std::cout << "produce" << std::endl;
int main()
std::thread consumers, producers;
consumers = std::thread(consumer);
producers = std::thread(producer);
consumers.join();
producers.join();
return 0;
条件变量需要使用while循环附加判断条件来解决条件变量的信号丢失和虚假唤醒问题。
3 采用C++封装的predicate方式解决虚假唤醒
难道我们每次都需要使用while循环和附加条件来操作条件变量吗,这岂不是很麻烦,在C++中其实有更好的封装,下面给出c++中condition_variable::wait的两种接口:
unconditional (1) | void wait (unique_lock& lck); |
predicate (2) | template void wait (unique_lock& lck, Predicate pred); |
采用第二种predicate的wait接口,只需要调用wait函数时在参数中直接添加附加条件即可,内部已经做好了while循环判断,直接使用即可。
下面给出具体代码:
#include <iostream>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <unistd.h>
std::mutex mutex;
std::condition_variable cv;
std::vector<int> vec;
void consumer()
sleep(1);
std::unique_lock<std::mutex> lock(mutex);
std::cout << "blocking on wait" << std::endl;
cv.wait(lock, [&]()return !vec.empty(); );
std::cout << "executed wait" << std::endl;
std::cout << "consume " << vec.size() << "\\n";
void produce()
std::unique_lock<std::mutex> lock(mutex);
vec.push_back(1);
cv.notify_all();
std::cout << "produce" << std::endl;
int main()
std::thread consumers, producers;
consumers = std::thread(consumer);
producers = std::thread(producer);
consumers.join();
producers.join();
return 0;
以上是关于[C++11 多线程同步] --- 条件变量的那些坑条件变量信号丢失和条件变量虚假唤醒(spurious wakeup)的主要内容,如果未能解决你的问题,请参考以下文章
[C++11 多线程同步] --- 条件变量的那些坑条件变量信号丢失和条件变量虚假唤醒(spurious wakeup)