c++中std::condition_variable最全用法归纳

2023-11-15

前言

建议阅读以下文章前需先对建立 std::thread 多线程与std::mutex 锁有一定程度的熟悉
std::thread最全用法归纳
std::mutex最全用法归纳

概括

使用 std::condition_variable 的 wait 会把目前的线程 thread 停下来并且等候事件通知,而在另一个线程中可以使用 std::condition_variable 的 notify_one 或 notify_all 发送通知那些正在等待的事件,在多线程中经常使用,以下将介绍 std::condition_variable 具体用法,并展示一些范例

condition_variable 常用成员函数:
- wait:阻塞当前线程直到条件变量被唤醒
- notify_one:通知一个正在等待的线程
- notify_all:通知所有正在等待的线程

使用 wait 必须搭配 std::unique_lock<std::mutex> 一起使用

范例1 用 notify_one 通知一个正在 wait 的线程

先梳理流程,该例先开一个新的线程 worker_thread 然后使用 wait 等待
此时 worker_thread 会阻塞(block)直到事件通知才会被唤醒
之后 main 主程序延迟个 5 ms 再使用 notify_one 发送
之后 worker_thread 收到 来自主线程的事件通知就离开 wait 继续往下 cout 完就结束该线程
主程序延迟 5ms 是避免一开始线程还没建立好来不及 wait 等待通知,主程序就先发送 notify_one 事件通知

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex m;
std::condition_variable cond_var;

void worker_thread()
{
    std::unique_lock<std::mutex> lock(m);
    std::cout << "worker_thread() wait\n";
    cond_var.wait(lock);

    // after the wait, we own the lock.
    std::cout << "worker_thread() is processing data\n";
}

int main()
{
    std::thread worker(worker_thread);

    std::this_thread::sleep_for(std::chrono::milliseconds(5));
    std::cout << "main() notify_one\n";
    cond_var.notify_one();

    worker.join();
    std::cout << "main() end\n";
}

输出如下

worker_thread() wait
main() notify_one
worker_thread() is processing data
main() end

范例2 用 notify_all 通知全部多个 wait 等待的线程

该例主要目的是建立5个线程并等待通知
之后主程序执行go函数里的cond_var.notify_all()通知所有正在等待的线程
这5个线程分别收到通知后从wait函数离开,之后检查ready为true则离开循环
接着打印thread id然后结束该线程

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex m;
std::condition_variable cond_var;
bool ready = false;

void print_id(int id) {
    std::unique_lock<std::mutex> lock(m);
    while (!ready) {
        cond_var.wait(lock);
    }
    std::cout << "thread " << id << '\n';
}

void go() {
    std::unique_lock<std::mutex> lock(m);
    ready = true;
    cond_var.notify_all();
}

int main()
{
    std::thread threads[5];
    // spawn 5 threads:
    for (int i=0; i<5; ++i)
        threads[i] = std::thread(print_id,i);

    std::cout << "5 threads ready to race...\n";
    go();

    for (auto& th : threads)
        th.join();

    return 0;
}

输出如下,可见这5个线程不按顺序地收到通知并且各别印出thread id

5 threads ready to race...
thread 4
thread 1
thread 2
thread 3
thread 0

该例中多使用了一个额外的 ready 变量来辅助判断,也间接介绍了cond_var.wait的另一种用法
使用一个 while 循环来不断检查 ready 变量,条件不成立的话就cond_var.wait继续等待
等到下次cond_var.wait被唤醒又会再度检查这个 ready 值,一直循环检查下去
该技巧在某些情形下可以避免假唤醒这个问题
简单说就是「cond_var.wait被唤醒后还要多判断一个 bool 变量,一定要条件成立才会结束等待,否则继续等待」
注意,其中while写法

while (!ready) {
    cond_var.wait(lock);
}

可以简化写成如下,亦即 wait 的另一种用法,多带一个关键词在第二个参数,范例3会介绍

cond_var.wait(lock, []{return ready;});

因为 wait 内部的实现方法如下,等价于上面这种写法

template<typename _Predicate>
void wait(unique_lock<mutex>& __lock, _Predicate __p)
{
    while (!__p())
        wait(__lock);
}

范例3 wait 等待通知且有条件的结束等待

范例2简单提及cond_var.wait带入第二个参数的用法,所以本范例来实际演练这个用法
该例中,worker_thread里的cond_var.wait第一参数传入一个 unique_lock 锁
第二个参数传入一个有返回的函数,来判断是否要停止等待,回传一个 bool 变量
如果回传 true ,condition_variable 停止等待、继续往下执行
如果回传 false ,则重新开始等待下一个通知
因此等价于while (!pred()) { wait(lock); }
注意 main 里是有一个 lock_guard 与 unique_lock,worker_thread 里有一个 unique_lock

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex m;
std::condition_variable cond_var;
std::string data;
bool ready = false;
bool processed = false;

void worker_thread()
{
    // Wait until main() sends data
    std::unique_lock<std::mutex> lock(m);
    std::cout << "worker_thread() wait\n";
    cond_var.wait(lock, []{return ready;});

    // after the wait, we own the lock.
    std::cout << "worker_thread() is processing data\n";
    data += " after processing";

    // Send data back to main()
    processed = true;
    std::cout << "worker_thread() signals data processing completed\n";

    // Manual unlocking is done before notifying, to avoid waking up
    // the waiting thread only to block again (see notify_one for details)
    lock.unlock();
    cond_var.notify_one();
}

int main()
{
    std::thread worker(worker_thread);

    data = "Example data";
    // send data to the worker thread
    {
        std::lock_guard<std::mutex> lock(m);
        ready = true;
        std::cout << "main() signals data ready for processing\n";
    }
    cond_var.notify_one();

    // wait for the worker
    {
        std::unique_lock<std::mutex> lock(m);
        cond_var.wait(lock, []{return processed;});
    }
    std::cout << "Back in main(), data = " << data << '\n';

    worker.join();
}

输出如下

main() signals data ready for processing
worker_thread() wait
worker_thread() is processing data
worker_thread() signals data processing completed
Back in main(), data = Example data after processing

范例4 典型的生产者与消费者案例

在设计模式(design pattern)中,这是一个典型的生产者与消费者(producer-consumer)的例子
范例里有一位生产者每1秒生产了1个东西放到 condvarQueue 里,
这个 condvarQueue 会在去通知消费者,消费者收到通知后从 queue 里拿出这个东西来做事情,看明白该例会有很大帮助

#include <iostream>
#include <thread>
#include <queue>
#include <chrono>
#include <mutex>
#include <condition_variable>

class condvarQueue
{
    std::queue<int> produced_nums;
    std::mutex m;
    std::condition_variable cond_var;
    bool done = false;
    bool notified = false;
public:
    void push(int i)
    {
        std::unique_lock<std::mutex> lock(m);
        produced_nums.push(i);
        notified = true;
        cond_var.notify_one();
    }

    template<typename Consumer>
    void consume(Consumer consumer)
    {
        std::unique_lock<std::mutex> lock(m);
        while (!done) {
            while (!notified) {  // loop to avoid spurious wakeups
                cond_var.wait(lock);
            }
            while (!produced_nums.empty()) {
                consumer(produced_nums.front());
                produced_nums.pop();
            }
            notified = false;
        }
    }

    void close()
    {
        {
            std::lock_guard<std::mutex> lock(m);
            done = true;
            notified = true;
        }
        cond_var.notify_one();
    }
};

int main()
{
    condvarQueue queue;

    std::thread producer([&]() {
        for (int i = 0; i < 5; ++i) {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            std::cout << "producing " << i << '\n';
            queue.push(i);
        }
        queue.close();
    });

    std::thread consumer([&]() {
         queue.consume([](int input){
             std::cout << "consuming " << input << '\n';
         });
    });

    producer.join();
    consumer.join();
}

输出如下

producing 0
consuming 0
producing 1
consuming 1
producing 2
consuming 2
producing 3
consuming 3
producing 4
consuming 4

归纳

等待的线程应有下列几个步骤:

  1. 获得 std::unique_lock 锁,并用该锁来保护共享变量
  2. 检查有没有满足结束等待的条件,以预防数据早已经被更新与被通知了
  3. 执行 wait 等待,wait 操作会自动释放该 mutex 并且暂停该线程
  4. 当 condition variable 通知时,该线程被唤醒,且该mutex自动被重新获得,该线程应该检查一些条件决定要不要继续等待

通知的线程应有下列几个步骤:

  1. 获取一个 std::mutex (通常通过std::lock_guard)
  2. 在上锁的范围内完成变量的修改
  3. 执行 std::condition_variable 的notify_one/notify_all
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c++中std::condition_variable最全用法归纳 的相关文章

  • 结构化绑定中缺少类型信息

    我刚刚了解了 C 中的结构化绑定 但有一件事我不喜欢 auto x y some func is that auto正在隐藏类型x and y 我得抬头看看some func的声明来了解类型x and y 或者 我可以写 T1 x T2 y
  • 如何将 std::string& 转换为 C# 引用字符串

    我正在尝试将 C 函数转换为std string参考C 我的 API 如下所示 void GetStringDemo std string str 理想情况下 我希望在 C 中看到类似的东西 void GetStringDemoWrap r
  • BASIC 中的 C 语言中的 PeekInt、PokeInt、Peek、Poke 等效项

    我想知道该命令的等效项是什么Peek and Poke 基本和其他变体 用 C 语言 类似PeekInt PokeInt 整数 涉及内存条的东西 我知道在 C 语言中有很多方法可以做到这一点 我正在尝试将基本程序移植到 C 语言 这只是使用
  • 调用 McAfee 病毒扫描引擎

    我收到客户的请求 要求使用他们服务器上的 McAfee 病毒扫描将病毒扫描集成到应用程序中 我做了一些调查 发现 McScan32 dll 是主要的扫描引擎 它导出各种看起来有用的函数 我还发现提到了 McAfee Scan Engine
  • 在一个数据访问层中处理多个连接字符串

    我有一个有趣的困境 我目前有一个数据访问层 它必须与多个域一起使用 并且每个域都有多个数据库存储库 具体取决于所调用的存储过程 目前 我只需使用 SWITCH 语句来确定应用程序正在运行的计算机 并从 Web config 返回适当的连接字
  • 为什么 GCC 不允许我创建“内联静态 std::stringstream”?

    我将直接前往 MCVE include
  • 从经典 ASP 调用 .Net C# DLL 方法

    我正在开发一个经典的 asp 项目 该项目需要将字符串发送到 DLL DLL 会将其序列化并发送到 Zebra 热敏打印机 我已经构建了我的 DLL 并使用它注册了regasm其次是 代码库这使得 IIS 能够识别它 虽然我可以设置我的对象
  • -webkit-box-shadow 与 QtWebKit 模糊?

    当时有什么方法可以实现 webkit box shadow 的工作模糊吗 看完这篇评论错误报告 https bugs webkit org show bug cgi id 23291 我认识到这仍然是一个问题 尽管错误报告被标记为RESOL
  • 需要帮助优化算法 - 两百万以下所有素数的总和

    我正在尝试做一个欧拉计划 http projecteuler net问题 我正在寻找 2 000 000 以下所有素数的总和 这就是我所拥有的 int main int argc char argv unsigned long int su
  • 人脸 API DetectAsync 错误

    我想创建一个简单的程序来使用 Microsoft Azure Face API 和 Visual Studio 2015 检测人脸 遵循 https social technet microsoft com wiki contents ar
  • 在 Unity 中实现 Fur with Shells 技术

    我正在尝试在 Unity 中实现皮毛贝壳技术 http developer download nvidia com SDK 10 5 direct3d Source Fur doc FurShellsAndFins pdf Fins 技术被
  • 使用 C# 中的 CsvHelper 将不同文化的 csv 解析为十进制

    C 中 CsvHelper 解析小数的问题 我创建了一个从 byte 而不是文件获取 csv 文件的类 并且它工作正常 public static List
  • 两个静态变量同名(两个不同的文件),并在任何其他文件中 extern 其中一个

    在一个文件中将变量声明为 static 并在另一个文件中进行 extern 声明 我认为这会在链接时出现错误 因为 extern 变量不会在任何对象中看到 因为在其他文件中声明的变量带有限定符 static 但不知何故 链接器 瑞萨 没有显
  • 如何在 Android 中使用 C# 生成的 RSA 公钥?

    我想在无法假定 HTTPS 可用的情况下确保 Android 应用程序和 C ASP NET 服务器之间的消息隐私 我想使用 RSA 来加密 Android 设备首次联系服务器时传输的对称密钥 RSA密钥对已在服务器上生成 私钥保存在服务器
  • 编译时展开 for 循环内的模板参数?

    维基百科 here http en wikipedia org wiki Template metaprogramming Compile time code optimization 给出了 for 循环的编译时展开 我想知道我们是否可以
  • 有没有办法让 doxygen 自动处理未记录的 C 代码?

    通常它会忽略未记录的 C 文件 但我想测试 Callgraph 功能 例如 您知道在不更改 C 文件的情况下解决此问题的方法吗 设置变量EXTRACT ALL YES在你的 Doxyfile 中
  • C# 中的 IPC 机制 - 用法和最佳实践

    不久前我在 Win32 代码中使用了 IPC 临界区 事件和信号量 NET环境下场景如何 是否有任何教程解释所有可用选项以及何时使用以及为什么 微软最近在IPC方面的东西是Windows 通信基础 http en wikipedia org
  • C++ 继承的内存布局

    如果我有两个类 一个类继承另一个类 并且子类仅包含函数 那么这两个类的内存布局是否相同 e g class Base int a b c class Derived public Base only functions 我读过编译器无法对数
  • 为什么C++代码执行速度比java慢?

    我最近用 Java 编写了一个计算密集型算法 然后将其翻译为 C 令我惊讶的是 C 的执行速度要慢得多 我现在已经编写了一个更短的 Java 测试程序和一个相应的 C 程序 见下文 我的原始代码具有大量数组访问功能 测试代码也是如此 C 的
  • 如何确定 CultureInfo 实例是否支持拉丁字符

    是否可以确定是否CultureInfo http msdn microsoft com en us library system globalization cultureinfo aspx我正在使用的实例是否基于拉丁字符集 我相信你可以使

随机推荐

  • ES6 新增的循环方法

    在 ES6 ECMAScript 2015 中 新增了一些循环方法 这些方法可以帮助我们更方便地遍历数组 字符串 Set Map 等数据结构 本文将介绍一些常用的 ES6 循环方法 for of 循环 for of 循环是一种遍历可迭代对象
  • 防火墙安全策略①

    防火墙 基本定义 防火墙是一种隔离 非授权用户在区域间 并过滤 对受保护网络有害流量或数据包 的设备 防火墙主要是借助硬件和软件的作用于内部和外部网络的环境间产生一种保护的屏障 从而实现对计算机不安全网络因素的阻断 只有在防火墙同意情况下
  • 如何将类数组转换为真正的数组

    开发过程中 有很多时候获取到的是类似数组的对象 比如元素的集合 elementcollection nodeList 以及今天开发时发现classList也是类数组 有时我们需要类数组去调用数组的方法 怎么办 一 遍历类数组 依次将元素放入
  • vue使用高德地图获取当前经纬度

    vue使用高德地图Api获取当前经纬度信息 在utils里面新建getLocation js 封装获取经纬度的公用方法 优化加载速度动态cdn引入高德地图 由于高德Api方法获取当前经纬度比较慢 如果需求是在获取到当前经纬度数据之后请求一些
  • JDBC数据库连接MySQL5.7

    1 首先准备mysql 和eclipse环境 在环境搭建好之后 从eclipse官网下载jdbc的驱动包 下载地址http dev mysql com downloads connector j 2 从下载的文件中取出mysql conne
  • 做电商的您还在为淘宝问题订单备注分类烦恼吗?

    做电商的您还在为淘宝问题订单备注分类烦恼吗 能够自动给淘宝订单添加旗子类型与备注的电商运营机器人 实现轻松管理淘宝问题订单 支持自动给订单添加旗子类型 自动给订单添加备注 解决问题订单处理缓慢 备注修改不及时等难题 使用UiBot之前 问题
  • Python javascript操作DOM

    文档对象模型 Document Object Model DOM 是一种用于HTML和XML文档的编程接口 它给文档提供了一种结构化的表示方法 可以改变文档的内容和呈现方式 我们最为关心的是 DOM把网页和脚本以及其他的编程语言联系了起来
  • 【元宇宙】智能手机万岁

    凭借出色的新设备 我们很快就能进人元字宙 想象这样的情景是很趣的 但是 至少到21世纪20年代 元宇宙时代的大多数设备很可能是我们已经在使用的设备 AR 和 VR 设备不仅面临重大的技术 财务和体验障碍 而且它们在上市后同样会面临币场反响冷
  • Esp8266 Node Mcu 一直乱码的问题详解

    最近一直在做项目 遇到的这个问题花了我很长时间 因此在这里写出自己的经历供大家参考 喜欢的可以点个赞 比较简单的方案 在Arduino上设置Node Mcu 1 打开文件 gt 首选项 复制这样一个网址 http arduino esp82
  • 关于Cache-Control: no-cache和no-store

    在公司上班的正真上班的第一天 发现的jsp页面上 设置了response HTTP头 是设置了这三个属性 Cache Control no cache Cache Control no store Expires 这三个属性都是和网页的缓存
  • OpenMPI:介绍与an'zhuang

    前言 在跑GLowdemo时发现没装OpenMPI 因此 只有装了吧 介绍 OpenMPI 1 是一种高性能消息传递库 最初是作为融合的技术和资源从其他几个项目 FT MPI LA MPI LAM MPI 以及 PACX MPI 它是MPI
  • pytest.fixture详解

    scope分为session package module class function级别 其中function也是包含测试类的测试方法的 package指在当前contest py的package下只执行一次 如果想在某个package
  • <深度学习基础> Batch Normalization

    Batch Normalization批归一化 BN优点 减少了人为选择参数 在某些情况下可以取消dropout和L2正则项参数 或者采取更小的L2正则项约束参数 减少了对学习率的要求 现在我们可以使用初始很大的学习率或者选择了较小的学习率
  • Unity3D中Update和FixedUpdate、LateUpdate的区别

    1 MonoBehaviour Update 更新 当MonoBehaviour启用时 其Update在每一帧被调用 2 MonoBehaviour FixedUpdate 固定更新 当MonoBehaviour启用时 其 FixedUpd
  • NOI2017搞基记

    一篇很长的流水账 写的长也就是因为考得好吧 去年写NOI游记的时候 就想着快点写完就好了 以后大概会再写一篇比较矫情的回顾一下竞赛的历程的吧 虽然我这种狗逼划水语文课代表的水平 大概激励人心的效果肯定赶不上hzwer的吧 DAY 2 在家排
  • linux下sublimetext的中文输入问题解决方法

    InputHelper插件 好奇怪哦 竟然一个文本编辑器在linux平台下竟然原生不能切换并使用系统自带的输入法 所以就有了一系列插件 看到网上各种方法 我觉得还是使用inputhelper这个插件最简单 使用这个插件可以通过Package
  • Java实现标题相似度计算,文本内容相似度匹配,Java通过SimHash计算标题文本内容相似度

    目录 一 前言 二 关于SimHash 补充知识 一 什么是海明距离 二 海明距离的应用 三 什么是编辑距离 三 SimHash算法的几何意义和原理 一 SimHash算法的几何意义 二 SimHash的计算原理 三 文本的相似度计算 四
  • Linux添加组播

    sudo route add net 224 1 1 0 netmask 255 255 255 0 dev ens33 转载于 https www cnblogs com tiandsp p 10985838 html
  • 鲁棒优化(4):通过yalmip中的kkt命令实现CCG两阶段鲁棒优化

    两阶段鲁棒优化的原理推导部分 已经较多的文章进行分析 目前大部分同学面临的问题是 子问题模型中存在的双线性项该如何处理 目前 主流方式是 采用对偶定理或KKT条件 将第二阶段的双层问题变成单层问题 简略的思想如下 首先是原始的两阶段模型 对
  • c++中std::condition_variable最全用法归纳

    前言 建议阅读以下文章前需先对建立 std thread 多线程与std mutex 锁有一定程度的熟悉 std thread最全用法归纳 std mutex最全用法归纳 概括 使用 std condition variable 的 wai