当前位置: 首页 > news >正文

[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;
}

相关文章:

  • 2022年中国研究生数学建模竞赛B题-方形件组批优化问题
  • Three读取pdb文件创建分子结构图实例
  • Spring Data JPA @Query注解
  • 猿创征文|Python快速刷题网站——牛客网 数据分析篇(十四)
  • 【元宇宙】元宇宙的定义、特征、要素及架构
  • 4、“组件协作“模式
  • 刚开始做自媒体,无从下手,有什么好的建议吗?
  • 2022 华为杯研赛F题思路 研究生数学建模
  • Opencv项目实战:12 你这背景太假啦!
  • python解CCF-CSP真题《202209-1 如此编码》
  • 数据分析可视化08 案例 2:历史数据变化趋势图设计
  • Redis-缓存击穿
  • 信息学奥赛一本通:2072:【例2.15】歌手大奖赛
  • 【Linux】进程控制 (万字)
  • ARMv9新特性:虚拟内存系统架构 (VMSA) 的增强功能
  • [js高手之路]搞清楚面向对象,必须要理解对象在创建过程中的内存表示
  • 《Javascript数据结构和算法》笔记-「字典和散列表」
  • ➹使用webpack配置多页面应用(MPA)
  • co模块的前端实现
  • JavaScript 事件——“事件类型”中“HTML5事件”的注意要点
  • Netty 4.1 源代码学习:线程模型
  • NLPIR语义挖掘平台推动行业大数据应用服务
  • Python利用正则抓取网页内容保存到本地
  • vue:响应原理
  • Web Storage相关
  • 搭建gitbook 和 访问权限认证
  • 坑!为什么View.startAnimation不起作用?
  • 理解 C# 泛型接口中的协变与逆变(抗变)
  • 那些被忽略的 JavaScript 数组方法细节
  • 软件开发学习的5大技巧,你知道吗?
  • 我有几个粽子,和一个故事
  • 用简单代码看卷积组块发展
  • 在Docker Swarm上部署Apache Storm:第1部分
  • [Shell 脚本] 备份网站文件至OSS服务(纯shell脚本无sdk) ...
  • 函数计算新功能-----支持C#函数
  • ​LeetCode解法汇总2304. 网格中的最小路径代价
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • ​学习一下,什么是预包装食品?​
  • #{}和${}的区别是什么 -- java面试
  • (3)选择元素——(14)接触DOM元素(Accessing DOM elements)
  • (附源码)ssm高校升本考试管理系统 毕业设计 201631
  • (附源码)计算机毕业设计SSM智慧停车系统
  • (七)Knockout 创建自定义绑定
  • (一)spring cloud微服务分布式云架构 - Spring Cloud简介
  • (轉貼)《OOD启思录》:61条面向对象设计的经验原则 (OO)
  • .libPaths()设置包加载目录
  • .Net 4.0并行库实用性演练
  • .net MVC中使用angularJs刷新页面数据列表
  • .NET 编写一个可以异步等待循环中任何一个部分的 Awaiter
  • .NET建议使用的大小写命名原则
  • .php文件都打不开,打不开php文件怎么办
  • ::
  • [ IO.File ] FileSystemWatcher
  • [1204 寻找子串位置] 解题报告
  • [20160807][系统设计的三次迭代]