条件变量 —C++17 多线程

C++标准库提供了条件变量的两种实现:std::condition_variablestd::condition_variable_any。它们都在标准库的头文件<condition_variable>内声明。两者都需配合互斥,方能提供妥当的同步操作。std::condition_variable仅限于与std::mutex一起使用;然而,只要某一类型符合成为互斥的最低标准,足以充当互斥,std::condition_variable_any即可与之配合使用,因此它的后缀是“_any”。由于std::condition_variable_any更加通用,它可能产生额外开销,涉及其性能、自身的体积或系统资源等,因此std::condition_variable应予优先采用,除非有必要令程序更灵活

#pragma once
std::mutex mut;
std::queue<data_chunk> data_queue;    ⇽-- - ①
std::condition_variable data_cond;
void data_preparation_thread()            // 由线程乙运行
{while (more_data_to_prepare()){data_chunk const data = prepare_data();{std::lock_guard<std::mutex> lk(mut);data_queue.push(data);    ⇽-- - ②}data_cond.notify_one();    ⇽-- - ③}
}
void data_processing_thread()           // 由线程甲运行
{while (true){std::unique_lock<std::mutex> lk(mut);    ⇽-- - ④data_cond.wait(lk, [] {return !data_queue.empty(); });    ⇽-- - ⑤data_chunk data = data_queue.front();data_queue.pop();lk.unlock();    ⇽-- - ⑥process(data);if (is_last_chunk(data))break;}
}

首先,我们使用std::queue队列在两个线程之间传递数据①。一旦线程乙准备好数据,就使用std::lock_guard锁住互斥以保护队列,并压入数据②。然后,线程乙调用std::condition_variable实例的成员函数notify_one(),通知线程甲③(如果它确实正等待着)。请注意,我们特地使用一个较小的代码块,放置压入数据的代码,目的是在解锁互斥后通知条件变量。若线程甲立刻觉醒,也无须等待互斥解锁,从而不会被阻塞。

同时,线程甲等待接收处理数据。这次,它先对互斥加锁,但使用的是std::unique_lock而非std::lock_guard④(我们很快会明白缘由)。线程甲在std::condition_variable实例上调用wait(),传入锁对象和一个lambda函数,后者用于表达需要等待成立的条件⑤。

本例中,[]{return !data_queue.empty();}是一个简单的lambda函数,它检查容器data_queue是否为空。若否,则说明已有数据备妥,存放在队列中等待处理。
接着,wait()在内部调用传入的lambda函数,判断条件是否成立:若成立(lambda函数返回true),则wait()返回;否则(lambda函数返回false),wait()解锁互斥,并令线程进入阻塞状态或等待状态。线程乙将数据准备好后,即调用notify_one()通知条件变量,线程甲随之从休眠中觉醒(阻塞解除),重新在互斥上获取锁,再次查验条件:若条件成立,则从wait()函数返回,而互斥仍被锁住;若条件不成立,则线程甲解锁互斥,并继续等待。我们舍弃std::lock_guard而采用std::unique_lock,原因就在这里:线程甲在等待期间,必须解锁互斥,而结束等待之后,必须重新加锁,但std::lock_guard无法提供这种灵活性。假设线程甲在休眠的时候,互斥依然被锁住,那么即使线程乙备妥了数据,也不能锁住互斥,无法将其添加到队列中。结果线程甲所等待的条件永远不能成立,它将无止境地等下去。
等待终止的条件判定需要查验队列是否非空⑤,为此,我们使用了简单的lambda函数。其实,也可以向wait()传递普通函数或可调用对象。本例只进行简单判定,实际上条件判定的函数有可能更加复杂,因而我们需事先另行写出。

那么,该判定函数就可以被直接传入,无须用lambda表达式包装。在wait()的调用期间,条件变量可以多次查验给定的条件,次数不受限制;在查验时,互斥总会被锁住;另外,当且仅当传入的判定函数返回true时(它判定条件成立),wait()才会立即返回。如果线程甲重新获得互斥,并且查验条件,而这一行为却不是直接响应线程乙的通知,则称之为伪唤醒(spurious wake)

按照C++标准的规定,这种伪唤醒出现的数量和频率都不确定。故此,若判定函数有副作用1,则不建议选取它来查验条件。倘若读者真的要这么做,就有可能多次产生副作用,所以必须准备好应对方法。譬如,每次被调用时,判定函数就顺带提高所属线程的优先级,该提升动作即产生的副作用。结果,多次伪唤醒可“意外地”令线程优先级变得非常高。


参考《C++并发编程实战(第2版)

条件变量 ---C++17 多线程相关推荐

  1. linux条件变量唤醒丢失,多线程编程精髓(三)

    本篇主要讲Linux环境下的多线程同步内核对象. (1)linux线程同步之互斥体:linux互斥体的用法与windows的临界区对象类似,使用数据结构 pthread_mutex_t表示互斥体对象( ...

  2. 互斥锁、条件变量、信号量浅析

    互斥锁.条件变量.信号量浅析 互斥锁与条件变量 条件变量是为了保证同步 条件变量用在多线程多任务同步的,一个线程完成了某一个动作就通过条件变量告诉别的线程,别的线程再进行某些动作(大家都在semtak ...

  3. boost条件变量使用

    C++ BOOST库 条件变量[多线程通信]机制 笔记 1相关理念 (1)类名 条件变量和互斥变量都是boost库中被封装的类. (2)条件变量 条件变量是thread库提供的一种等待线程同步的机制, ...

  4. linux C++ 多线程使用pthread_cond 条件变量

    1. 背景 多线程中经常需要使用到锁(pthread_mutex_t)来完成多个线程之间的互斥操作. 但是互斥锁有一个明显到缺点: 只有两种状态,锁定和非锁定. 而条件变量则通过允许线程阻塞并等待另一 ...

  5. java线程条件变量_使用条件变量(多线程笔记)

    条件变量属性: 使用条件变量可以以原子方式阻塞线程,知道某个特定条件为真为止.条件变量始终与互斥锁一起使用. 使用条件变量,线程可以以原子方式阻塞,知道满足某个条件为止.对掉件的测试时在互斥锁的保护下 ...

  6. linux C语言多线程库pthread中条件变量的正确用法逐步详解

    linux C语言多线程库pthread中条件变量的正确用法: 了解pthread常用多线程API和pthread互斥锁,但是对条件变量完全不知道或者不完全了解的人群. 关于条件变量的典型应用,可以参 ...

  7. Linux多线程编程---线程间同步(互斥锁、条件变量、信号量和读写锁)

    本篇博文转自http://zhangxiaoya.github.io/2015/05/15/multi-thread-of-c-program-language-on-linux/ Linux下提供了 ...

  8. 【C++】多线程互斥锁、条件变量

    我们了解互斥量和条件变量之前,我们先来看一下为什么要有互斥量和条件变量这两个东西,了解为什么有这两东西之后,理解起来后面的东西就简单很多了!!! 先来看下面这段简单的代码: int g_num = 0 ...

  9. Linux多线程同步------条件变量

    先来看下<Linux高性能服务器编程>中对条件变量的描述: 上述话可以总结为: 多线程中某一个线程依赖于另外一个线程对共享数据的改变时,就可以使用条件变量! 用消费者生产者的来理解条件变量 ...

最新文章

  1. 2021年大数据常用语言Scala(二十五):函数式编程 排序
  2. 高级软件工程第二次作业
  3. ubuntu创建新用户名和密码以及查看删除用户
  4. 买房贷款时为什么银行让客户选等额本息,这是不是个坑?
  5. rust怎么造双层_DIY双层电路板 制作详解
  6. 办公用品管理系统VB——模块
  7. 中国人口最多的姓氏排行
  8. DAY 3 字符串、列表、字典练习 - 班级按成绩分组小程序快递分拣小程序
  9. wireshark win7无响应
  10. linux安装mysql deb_Ubuntu下通过deb的bundle文件安装MySQL
  11. windows自带日文输入法突然显示假名与敲击的按键不一致
  12. Android中模拟点击软件的实现原理探究
  13. lemming games 1!! hdlbits
  14. C# 对JS解析AJX请求JSON并绑定到html页面的一些心得
  15. linux debian vi,Debian 安装 vim
  16. 如何自己给电动车电池补水?DIY!超详细多图教程
  17. 头条号音频项目,轻松日入200+
  18. windows10删除桌面右键菜单
  19. 《一次别离》:幸好还有《古兰经》
  20. STM32CubeIDE(十一):FreeRTOS选项中Disable、CMSIS_V1和CMSIS_V2的区别

热门文章

  1. java二维数组水平翻转,C 语言 利用二维数组实现对输入的数组进行翻转
  2. 电饼锅的样式图片价格_进口珐琅铸铁锅专场,精致小厨娘们来康康!
  3. java同名函数_浅谈Java 继承接口同名函数问题
  4. 在手机测试html,借助 IIS 管理器 -- 用手机测试HTML页面
  5. 解决问题E: 无法获得锁 /var/lib/dpkg/lock - open (11: 资源暂时不可用) E: 无法锁定管理目录,
  6. linux epoll 开发指南-【ffrpc源码解析】
  7. Python 爬虫利器二之 Beautiful Soup 的用法
  8. 关于检测手机信号强度,wifi信号强度以及检测周围wifi热点的一个小例子
  9. Bash中执行存储过程或普通的SQL命令
  10. 微信小程序保存图片到相册;uni-app小程序保存网络图片到相册;小程序保存图片到相册拒绝授权后重新拉起授权;保存图片到系统相册;小程序保存图片测试可以,真机保存图片失败