IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++并发编程之 std::condition_variable的虚假唤醒 -> 正文阅读

[C++知识库]C++并发编程之 std::condition_variable的虚假唤醒

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的时候多注意下,应该能规避这种坑的,还是非常的银杏。最后,有问题的话,望大佬斧正啦~~~

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-18 17:12:27  更:2021-10-18 17:14:43 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 3:41:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码