首先,我们在创建一个生产者和消费者的模型,生产者生产数据存放在容器中,而消费者,从容器中拿到数据,并且每次释放第一个数据。具体代码如下:
/************************************************************************* > 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的尿性,他没事会调起你这个消费线程试试,看看你是不是还活着,那么如果这时候这些唤醒就是无效的,因为没有数据可以消费,就是玩~~~ 嘿~~~!!~
这种就是我们不需要看到的,因为没事我不想调用它。怎么解决呢?这就是虚假唤醒了。
前面我们介绍了虚假唤醒的由来,但是有问题就有解决的办法。我们可以在线程中由阻塞状态到唤醒后的状态增加附加条件,如果不满足条件则继续等待,如下所示:
/************************************************************************* > 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******************************/
虚假唤醒是比较容易犯的问题,但是我们在写bug的时候多注意下,应该能规避这种坑的,还是非常的银杏。最后,有问题的话,望大佬斧正啦~~~