[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)

[C++11 多线程同步] --- 条件变量

[C++11 多线程同步] --- 条件变量

[C++11 多线程同步] --- 条件变量

[多线程]C++11多线程-条件变量(std::condition_variable)

并发编程多线程程序同步策略