1、虚假唤醒产生原因
首先,我们在创建一个生产者和消费者的模型,生产者生产数据存放在容器中,而消费者,从容器中拿到数据,并且每次释放第一个数据。具体代码如下:
/*************************************************************************
> File Name: thread_spurious_wakeup.cpp
> Author: 小和尚念经敲木鱼
> Mail:
> Created Time: Sat 16 Oct 2021 06:25:49 PM CST
***********************************************************************/
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
#include <vector>
using namespace std;
/************************************************************************
* 文件说明
* 1.虚假唤醒笔记demo
* 2.后续补充知识点
************************************************************************/
std::mutex g_Mtx;
std::vector<int> g_Data;
std::condition_variable g_ConVar;
static int g_produce_count = 0;
void ConsumeData()
{
while (1)
{
{
std::unique_lock<std::mutex> lck(g_Mtx);
g_ConVar.wait(lck);//如果先执行的是ProductData,但是ConsumeData没有执行到wait则,会丢失nofify信号。则一直阻塞
std::cout << "Data size = " << g_Data.size() << "\tData =" << *g_Data.begin()<< std::endl;
g_Data.erase(g_Data.begin());
}
std::chrono::milliseconds sleep_time(1);
std::this_thread::sleep_for(sleep_time);
}
}
void ProduceData()
{
while (1)
{
{
std::unique_lock<std::mutex> lck(g_Mtx);
g_Data.push_back(g_produce_count);
g_produce_count++;
g_ConVar.notify_all();
}
std::chrono::milliseconds sleep_time(5);
std::this_thread::sleep_for(sleep_time);
}
}
int main(int agc,char * agv[])
{
std::cout << "[" << __FILE__ << "]" << " thread note" << std::endl;
std::thread thread1(ProduceData);
std::thread thread2(ConsumeData);
if (thread1.joinable())
thread1.join();
if (thread2.joinable())
thread2.join();
return 0;
}
/******************************end of file******************************/
好的,从上面可以看到普通的生产-消费者模型,我们已经都构建好了。接下来提出问题,如果CPU线程调度,先调度了生产者线程,但是消费线程还在创建中,并没有执行到wait阻塞这等待,那么我们程序是不是就丢失了一个通知信号?因为我在生产者和消费者都是用的while循环,所以可以说,可以等下次信号,但是我们是不是丢了第一次的通知信号?那么我们怎么规避丢失第一次信号这个问题呢?
/*************************************************************************
> File Name: thread_spurious_wakeup.cpp
> Author: 小和尚念经敲木鱼
> Mail:
> Created Time: Sat 16 Oct 2021 06:25:49 PM CST
***********************************************************************/
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
#include <vector>
using namespace std;
/************************************************************************
* 文件说明
* 1.虚假唤醒笔记demo
* 2.后续补充知识点
************************************************************************/
std::mutex g_Mtx;
std::vector<int> g_Data;
std::condition_variable g_ConVar;
static int g_produce_count = 0;
void ConsumeData()
{
while (1)
{
{
std::unique_lock<std::mutex> lck(g_Mtx);
if (g_Data.empty()) {
g_ConVar.wait(lck);
}
std::cout << "Data size = " << g_Data.size() << "\tData =" << *g_Data.begin()<< std::endl;
g_Data.erase(g_Data.begin());
}
std::chrono::milliseconds sleep_time(1);
std::this_thread::sleep_for(sleep_time);
}
}
void ProduceData()
{
while (1)
{
{
std::unique_lock<std::mutex> lck(g_Mtx);
g_Data.push_back(g_produce_count);
g_produce_count++;
g_ConVar.notify_all();
}
std::chrono::milliseconds sleep_time(5);
std::this_thread::sleep_for(sleep_time);
}
}
int main(int agc,char * agv[])
{
std::cout << "[" << __FILE__ << "]" << " thread note" << std::endl;
std::thread thread1(ProduceData);
std::thread thread2(ConsumeData);
if (thread1.joinable())
thread1.join();
if (thread2.joinable())
thread2.join();
return 0;
}
/******************************end of file******************************/
好的,从上面的代码中,我们加了如下一行的处理:
if (g_Data.empty()) {
g_ConVar.wait(lck);
}
这行处理是,只要线程执行,并获取到资源,则进行数据的判断,如果判断数据队列为空,则就等待,那么我们是不是可以理解为:通过判定消息队列是否有数据,再进行挂起线程,这样就可以规避掉丢失信号的这种情况了。但是这时候还有一个问题,按照CPU的尿性,他没事会调起你这个消费线程试试,看看你是不是还活着,那么如果这时候这些唤醒就是无效的,因为没有数据可以消费,就是玩~~~ 嘿~~~!!~
这种就是我们不需要看到的,因为没事我不想调用它。怎么解决呢?这就是虚假唤醒了。
2、规避虚假唤醒
前面我们介绍了虚假唤醒的由来,但是有问题就有解决的办法。我们可以在线程中由阻塞状态到唤醒后的状态增加附加条件,如果不满足条件则继续等待,如下所示:
/*************************************************************************
> File Name: thread_spurious_wakeup.cpp
> Author: 小和尚念经敲木鱼
> Mail:
> Created Time: Sat 16 Oct 2021 06:25:49 PM CST
***********************************************************************/
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
#include <vector>
using namespace std;
/************************************************************************
* 文件说明
* 1.虚假唤醒笔记demo
* 2.后续补充知识点
************************************************************************/
std::mutex g_Mtx;
std::vector<int> g_Data;
std::condition_variable g_ConVar;
static int g_produce_count = 0;
void ConsumeData()
{
while (1)
{
{
std::unique_lock<std::mutex> lck(g_Mtx);
while (g_Data.empty()) { //规避虚假唤醒
g_ConVar.wait(lck);
}
std::cout << "Data size = " << g_Data.size() << "\tData =" << *g_Data.begin()<< std::endl;
g_Data.erase(g_Data.begin());
}
std::chrono::milliseconds sleep_time(1);
std::this_thread::sleep_for(sleep_time);
}
}
void ProduceData()
{
while (1)
{
{
std::unique_lock<std::mutex> lck(g_Mtx);
g_Data.push_back(g_produce_count);
g_produce_count++;
g_ConVar.notify_all();
}
std::chrono::milliseconds sleep_time(5);
std::this_thread::sleep_for(sleep_time);
}
}
int main(int agc,char * agv[])
{
std::cout << "[" << __FILE__ << "]" << " thread note" << std::endl;
std::thread thread1(ProduceData);
std::thread thread2(ConsumeData);
if (thread1.joinable())
thread1.join();
if (thread2.joinable())
thread2.join();
return 0;
}
/******************************end of file******************************/
3、总结
虚假唤醒是比较容易犯的问题,但是我们在写bug的时候多注意下,应该能规避这种坑的,还是非常的银杏。最后,有问题的话,望大佬斧正啦~~~