C++条件变量std::condition_variable和虚假唤醒

2 篇文章 0 订阅

std::condition_variable

std::condition_variable是C++11新加入的用于多个线程之间同步的一种机制,头文件是<condition_variable>,类似于Linux下的pthread_cond_t,只有当条件满足时线程才会被触发,不满足时线程被阻塞,需要配合std::unique_lock来使用,例如以下:

#include <iostream>
#include<thread>
#include<deque>              
#include <mutex>   
#include <map>   
#include <string>  
#include <condition_variable> 


std::mutex g_mtx; // 全局互斥锁.
std::condition_variable g_cond; // 全局条件变量.
std::deque<int> g_deque;

void consumeFunc(int id)
{
	while (true)
	{
		std::unique_lock <std::mutex> lck(g_mtx);
		while (g_deque.empty())
		{		
			g_cond.wait(lck);
		}
		int aa = g_deque.back();
		std::cout << "thread " << id << "  " << aa << '\n';
		g_deque.pop_back();
		lck.unlock();
	}
}
void produceFunc()
{
	while (true)
	{
		std::unique_lock <std::mutex> lck(g_mtx);
		if (g_deque.empty())
		{
			g_deque.push_back(1);
			g_cond.notify_all(); // 唤醒所有线程.
		}
		lck.unlock();
		std::this_thread::sleep_for(std::chrono::seconds(1));
	}
}


int main()
{
	std::thread consumeThread[10];

	for (int i = 0; i < 10; ++i)
		consumeThread[i] = std::thread(consumeFunc, i);

	std::thread produThread;

	produThread = std::thread(produceFunc);

	for (int i = 0; i < 10; ++i)
	{
		if (consumeThread[i].joinable())
		{
			consumeThread[i].join();
		}
	}

	if (produThread.joinable())
	{
		produThread.join();
	}

	return 0;
}

生产者线程不断生产数据并放入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(); });

 

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值