C++ 条件变量的使用

news2025/7/11 2:41:05

绪论

并发编程纷繁复杂,其中用于线程同步的主要工具——条件变量,虽然精悍,但是要想正确灵活的运用却并不容易。
对于条件变量的理解有三个难点:

  1. 为什么wait函数需要将解锁和阻塞、唤醒和上锁这两对操作编程原子的?
  2. 为什么wait函数需要配合while进行使用?
  3. 通知线程是应该先notifyunlock还是先unlocknotify

希望大家看完下面的介绍能够得到想要的答案。想要了解更多关于C++并发编程信息可以移步的我仓库:C++并发编程

条件变量

C++提供了两种条件变量的实现:std::condition_variablestd::condition_variable_any。前者只能和std::mutex配合使用,后者只需要符合互斥的标准即可。因为std::condition_variable_any更通用,所以可能产生额外的开销,如果没什么特殊需要,尽可能使用std::condition_variable

条件变量是非常重要的线程同步的手段(目前我认为是最重要的),因此对其的深入理解至关重要。

  • 条件变量总是和互斥一起配合使用,互斥用于保护共享数据,条件变量用于

    1. 通知(通知线程)
    2. 判断共享数据是否满足条件(等待线程)
  • 通知线程往往先通过互斥保护共享数据,对数据进行一定的修改后再发送通知(notify_one()、notify_all())。需要注意的是我们应尽可能在临界区内发送通知,从而避免可能出现的优先级翻转和条件变量失效问题。虽然临界区外通知可以让等待线程一旦被唤醒就能立即解锁互斥查看是否满足情况,但是在Pthread进行wait morphint后基本上两者没有性能上的差距。详细的分析可以参考博客:条件变量用例–解锁与signal的顺序问题。

    • notify_one()理论上只会唤醒一个等待线程,适用于共享变量数量发生变化的情况,例如通知消息队列中的消息个数增加。
    • notify_all()会唤醒所有等待该条件变量的线程,适用于共享变量状态发生变化的情况,例如通知所有工作线程开始计算。
  • 等待线程先获得互斥,然后将锁和判定条件传递给wait函数等待返回。

    • wait函数首先会根据判断条件判断是否满足条件(返回true

      • 如果满足条件,则直接返回(互斥依旧上锁)

      • 如果不满足条件,则阻塞等待,并解锁互斥(让其他线程得以修改共享数据的状态)。直到被notify函数唤醒,再次上锁,判断条件是否满足。这里的阻塞和解锁、唤醒和上锁都是原子的,就是为了避免两个动作分别执行出现的条件竞态。

        1. 解锁和阻塞是原子的:lock → !pred() → unlock → sleep;如果变量的改变以及唤醒事件发生在unlock和sleep中间,那么你不会检测到,也就是错过了这次唤醒。假如下次唤醒依赖于此次唤醒的成功(也就是说不会主动唤醒第二次),那么将发生死锁。
        2. 唤醒和上锁是原子的:wakeup → lock → !pred :如果条件在wakeup和lock之间从满足变成了不满足(不是因为其他等待线程修改,而是因为负责唤醒的线程自己再次修改了条件),那么此次唤醒将失败。假如后面条件的再次满足依赖于此次条件满足成功(也就是说条件不会再主动满足),那么将发生死锁。

        需要理解的是上面的死锁的出现是有限定条件的(例如唤醒之间的依赖、条件满足的依赖),虽然大多数情况下没有这么严格的条件,但是工具本身需要避免这种危险的情况。

        原子操作保证了重要的唤醒和条件满足都能够至少被一个等待线程看到。

      • 可以看到wait函数内部需要解锁互斥,所以就不能使用不提供unlock函数的lock_guard,而应该使用和互斥有相同接口的unique_lock

    • 其实C++的线程库是对pthread库的封装,因此也可以像pthread库一样只传入互斥,解锁并等待通知,一旦接收到通知后再上锁,然后在一个while循环中进行判断。

      while (!pred()) {
          cond_.wait(lk);  //调用pthread_cond_wait
      }
      

      对于传入判定条件的版本,其实内部也是这样的一个封装罢了。

  • 之所以说notify_one()理论上只会唤醒一个等待线程是因为存在调用一次notify_one()却唤醒了多个线程的可能性,甚至有时候没有调用notify等待线程都被唤醒,称这种意外唤醒等待线程的情况为伪唤醒。按照C++标准的规定,这种伪唤醒出现的数量和频率都不确定,因此要求等待线程的判定函数不能有副作用(可重用),并且需要在唤醒后再次判断条件是否满足,如果不满足则需要重新等待。这也是为什么上面的代码使用while进行条件判断而不是if的原因。

消息队列

//
// Created by edward on 22-11-16.
// use condtion_variable to genenrate a thread safe message queue
//

#include "utils.h"
#include <mutex>
#include <queue>
#include <condition_variable>
#include <iostream>
#include <thread>
#include <string>


template<typename T>
class MessageQueue {
public:
    void push(T t) {
        std::lock_guard lk(mtx_);       //互斥保护数据
        queue_.push(std::move(t));
        cond_.notify_one();				//临界区内发送通知,避免优先级反转和条件变量失效
    }
    T pop() {
        T frnt;
        std::unique_lock lk(mtx_);
        cond_.wait(lk, [&](){
            return !queue_.empty();
        });
        frnt = std::move(queue_.front());
        queue_.pop();
        return frnt;
    }
private:
    mutable std::mutex mtx_;
    mutable std::condition_variable cond_;
    std::queue<T> queue_;
};

using namespace std;

template<typename T>
void data_prepare(MessageQueue<T> &messageQueue) {
    T t;
    while (cin >> t) {
        messageQueue.push(std::move(t));
    }
}

template<typename T>
void data_process(MessageQueue<T> &messageQueue) {
    T t;
    int idx = 0;
    while (true) {
        t = messageQueue.pop(); //数据的处理在临界区外
        edward::print("[", idx++, "]:", t);
    }
}

int main() {
    MessageQueue<string> messageQueue;
    edward::print("test begin:");
    thread preparer(data_prepare<string>, ref(messageQueue));
    thread processer(data_process<string>, ref(messageQueue));
    preparer.join();
    //不用等待processer,如果preparer结束,则直接推出进程
    return 0;
}

运行结果

在这里插入图片描述

其中用到了我自己写的库函数头文件utils,如果想要了解更多信息可以移步C++ 工具函数库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/9578.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MCMC学习笔记-马尔科夫链概述

参考文章&#xff1a;MCMC(二)马尔科夫链 - 刘建平Pinard - 博客园 写给小白看的马尔科夫链&#xff08;Markov Chain&#xff09;最佳入门教程_许进进的博客-CSDN博客_markov链 目录 1.马尔科夫链概述 1.1股票市场模型 2.马尔科夫链模型状态转移矩阵的性质 (本节重点) 2.…

小程序容器技术加持下,企业自主打造小程序生态

小程序是一种不用下载就能使用的应用&#xff0c;也是一项门槛非常高的创新&#xff0c;经过将近两年的发展&#xff0c;已经构造了新的小程序开发环境和开发者生态。 据对公开资料进行统计&#xff0c;2021年全网小程序数量已超700万&#xff0c;其中微信小程序开发者突破300…

java使用world模板动态生成PDF文件

根据项目需求&#xff0c;需要用到一个功能&#xff0c;根据页面参数需要动态的生成一个world&#xff0c;并将world生成两份PDF文件&#xff0c;一份正式文件&#xff0c;一份临时的电子文件&#xff08;带有二维码&#xff0c;扫描可以下载正式文件的电子版本&#xff09;。同…

JAVA开发(nginx)

主要描述下面4个内容&#xff1a; 1.Nginx的正向代理和反向代理 2.Nginx的动静分离 3.Nginx的负载均衡 4.Nginx的配置详解 解释&#xff1a; Nginx的正向代理&#xff1a;代理的是客户端。 Nginx的反向代理&#xff1a;代理的是服务端。在web服务中&#xff0c;一般都是…

2022年“网络安全”赛项驻马店市赛选拔赛 任务书

2022年“网络安全”赛项驻马店市赛选拔赛 一、竞赛时间 共计3小时。 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第一阶段单兵模式系统渗透测试 任务一 Windows操作系统渗透测试 100分钟 100 任务二 Linux操作系统渗透测试 150 任务三 数字取证调查 150 任务四 …

vscode不能打开终端问题

遇到vscode不能打开终端问题&#xff0c;一直以为是安全软件限制问题&#xff0c;也没搜到解决方案&#xff0c;因为影响也不大&#xff0c;就没有管。 最近&#xff0c;要用vscode调试代码&#xff0c;发现不能打开终端&#xff0c;没法玩了&#xff0c;又来看这个问题&#…

【图像隐藏】基于matlab像素预测和位平面压缩的加密图像可逆数据隐藏【含Matlab源码 2218期】

⛄一、加密图像可逆数据隐藏简介 1 信息隐藏技术概述 信息隐藏技术是把秘密信息隐藏在多媒体信息中的一种方法。图像是最适合信息隐藏的数据载体。信息隐藏的方法主要有数字水印技术、隐写术等。 信息隐藏可以分为有损信息隐藏与可逆信息隐藏信息隐藏技术, 区别在于接收端是否…

【虚幻引擎UE】UE5 简单实现范围计算圆圈绘制

先来看看可以实现的效果&#xff1a; 一、实现快速绘制圆圈的C函数 .cpp文件 #include "drawPolygon.h" #include "Components/LineBatchComponent.h" #include "Engine/World.h" #include "EngineGlobals.h" #include "Prim…

墨西哥专线详解:墨西哥专线时效多久,墨西哥专线价格多少?

墨西哥专线是国内直飞墨西哥或者海运墨西哥的专线物流服务&#xff0c;从中国到墨西哥的物流大约需要3到30天。不同的运输方式到墨西哥的时间不同。国际快递是最快的。通常3到5天就可以完成目的地派送&#xff0c;最慢的是海运专线&#xff0c;到墨西哥大约需要20到30天才能到达…

隐式神经表示二:超分网络学习傅里叶系数Local Texture Estimator for Implicit Representation Function

文章目录1. Local Texture Estimator for Implicit Representation Function1. 通过隐式神经网络表示方法 实现 超分辨率。2. 在编码器和解码器之间作者引入一个 local texture estimator3. 代码分析整体框架生成图像特征&#xff0c;编码器是一个常规的卷积网络&#xff0c;文…

【Pytorch with fastai】第 8 章 :协同过滤深入探讨

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

十四、Java String 类

Java String 类 字符串广泛应用 在 Java 编程中&#xff0c;在 Java 中字符串属于对象&#xff0c;Java 提供了 String 类来创建和操作字符串。 创建字符串 创建字符串最简单的方式如下: String str "xxx"; 在代码中遇到字符串常量时&#xff0c;这里的值是 &q…

大数据开源平台好在哪里?

当前是大数据发展时代&#xff0c;对于企业而言需要紧紧抓住契机乘势而上&#xff0c;利用好内部数据做好数字化转型&#xff0c;可以为企业带来更高的发展空间。大数据开源平台是助力企业提升办公效率的软件平台&#xff0c;那么&#xff0c;哪里有这样的大数据开源平台&#…

关于Git使用:fatal: Could not read from remote repository.的报错问题解决

目录 一&#xff1a;问题描述 二&#xff1a;解决过程 1&#xff0c;增加账号及邮箱 2&#xff0c;添加秘钥&#xff1a; 3&#xff0c;获取公钥并将其设置到云效里面 4&#xff0c;宝塔终端 解除密码 三&#xff1a;解决截图 一&#xff1a;问题描述 我们公司的版本仓…

【布隆过滤器】世界上大概有1 亿种小蛋糕,客户康宝要求这辈子不吃重复的小蛋糕。

文章目录需求概念思想问题优点缺点应用手写布隆过滤器补充需求 现在客户康宝有一个需求&#xff1a;世界上大概有 1 亿 种小蛋糕&#xff0c;康宝要求这辈子不吃重复种类的小蛋糕。 因为小蛋糕的种类很大可能只会增加&#xff0c;而不会减少&#xff0c;面对这种大数据量的要…

静态分析 Qt Ceator 组织的工程代码

文章目录Missing reference in range-for with non trivial type (QString) [clazy-range-loop]Slots named on_foo_bar are error prone [clazy-connect-by-name]Call to virtual method FlowLayout::takeAt during destruction bypasses virtual dispatch [clang-analyzer-op…

目的和目标的差异|丰田自动工程完结的目的、目标、应用化的意义和明确、二

目的和目标的差异|丰田自动工程完结的目的、目标、应用化的意义和明确、二 业务的方式改废|工作的目的、目标、输出的明确化 业务改善的一种方法被称为业务改废。意思是更好地改善现有的业务&#xff0c;废除不必要的业务。 使用这种方法&#xff0c;首先要明确想要改变和废除…

初学Java,遇错就懵,这类问题到底怎么处理呢?!

前言 众所周知&#xff0c;壹哥在干Java的路上已经越走越远&#xff0c;越陷越深&#xff0c;最近无意刷知乎时看到一位初学Java的迷惑少年&#xff0c;被报错干懵啦&#xff01;&#xff01;&#xff01; 初学Java的同学&#xff0c;遇到报错一定不要慌&#xff0c;首先检查…

Android App规范处理中版本设置、发布模式、给数据集SQLite加密的讲解及使用(附源码 超详细必看)

运行有问题或需要源码请点赞关注收藏后评论区留言~~~ 一、版本设置 每个App都有三个基础信息&#xff0c;第一个是App的图标&#xff0c;第二个是App的名称&#xff0c;第三个是App的版本号。 一旦安装了某个版本的App&#xff0c;那么之后只能安装版本更新的同名App&#xf…