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

条件变量 —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版)

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

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

相关文章

关于在asp.net中textbox文本输入框中的汉语标点符号显示位置的问题

在asp.net中的服务器控件textbox中输入中文标点符号&#xff0c;位置处于输入框中间&#xff0c;而不是靠在左下角&#xff0c;解决办法&#xff1a;把字体样式设置为其它&#xff0c;比如&#xff1a;微软雅黑。这个问题&#xff0c;仅在宋体的时候出现过。 转载于:https://ww…

考验

如果不做网站&#xff0c;可以做着不错的工作&#xff0c;过着安逸的生活&#xff0c;可是&#xff0c;我不想年老的时候后悔&#xff1a;这一生竟然没有为自己的理想拼搏过!仅这一个理由&#xff0c;足以让我坚强地面对任何考验!博客园的发展需要付出更多努力&#xff0c;开始…

汇编常用命令、指令一览

MOV&#xff08;MOVe&#xff09; 传送指令P28 PUSH 入栈指令P32 POP 出栈指令P33 XCHG&#xff08;eXCHanG&#xff09; 交换指令P34 XLAT&#xff08;TRANSLATE&#xff09; 换码指令P34 LEA &#xff08;Load Effective Address&#xff09; 有效地址送…

std::future ---C++17 多线程

std::future —C17 多线程 std::future C标准程序库使用future来模拟这类一次性事件&#xff1a;若线程需等待某个特定的一次性事件发生&#xff0c;则会以恰当的方式取得一个future&#xff0c;它代表目标事件&#xff1b;接着&#xff0c;该线程就能一边执行其他任务&#…

VNCserver在Fedora上配置过程

前言&#xff1a;一直想写一下vncserver在redhat下详细配置过程&#xff0c;以帮助一些向我有同样需求却有懒得去读man page的朋友&#xff0c;后来在www.fedoranews.org上发现已经有人写了一个教程&#xff0c;并且还不错。干脆翻译算了。大家可以直接去阅原文&#xff0c;我这…

学好英语的42个经典要诀(完整版)

第一要诀&#xff1a;收听英语气象报告 有些教学录音带为配合初学者的学习&#xff0c;故意放慢语速&#xff0c;这对英语听力的训练是不够的。如果听语速正常的英语&#xff0c;初学者又会感到力不从心。英语气象报告的速度虽快&#xff0c;但词汇简单固定&#xff0c;内容单纯…

std::packaged_task() ---C++17 并发编程

std::packaged_task() —C17 并发编程 std::packaged_task<>连结了future对象与函数&#xff08;或可调用对象&#xff09;。 std::packaged_task<>对象在执行任务时&#xff0c;会调用关联的函数&#xff08;或可调用对象&#xff09;&#xff0c;把返回值保存为…

js分页--存储数据并进行分页

//分页方法var page function(){this.v {o:null,//ul父级层home:null,previous:null,next:null,last:null, list:[],pageSize:10,pageIndex:0,pageCount:0,rowCount:0};this.init function(){var _this this;_this.v.o.find("li").each(function(i,o){_this.v.…

c/c++面试试题(一)

1.求下面函数的返回值&#xff08;微软&#xff09;int func(x) { int countx 0; while(x) { countx ; x x&(x-1); } return countx; } 假定x 9999。 答案&#xff1a;8思路&#xff1a;将x转化为2进制&#xff0c;看含有的1…

react(78)--vs打开setting.json

1.ctrl shift p 2.输入setting 3.找到这一项

stdspan ---C++20

std::span —C20 std::span的定义 template<class T,std::size_t Extent std::dynamic_extent > class span;std::span是指向一组连续的对象的对象, 是一个视图view, 不是一个拥有者owner 一组连续的对象可以是 C 数组, 带着大小的指针, std::array, 或者 std::strin…

2. Get the codes from GIT

Clone the code from git. Click the “GitEx Clone”. Paste the url into the “Repository to clone”. You can get the route from git repository from it: https://msstash.companydomainname.com/ .Find the project which you want to download and then click the “…

按钮控件数组

Public Class ButtonArray Inherits System.Collections.CollectionBase Private ReadOnly HostForm As System.Windows.Forms.Form 创建类的构造函数。 Visual Basic Public Sub New(ByVal host As System.Windows.Forms.Form) HostForm host Me.Add…

c/c++面试试题(二)

21. New delete 与malloc free 的联系与区别?答案&#xff1a;都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分配的字节数并且不能初始化对象&#xff0c;new 会自动调用对象的构造函数。delete 会调用对象的destructor&#xff0c;而free 不会调用对象的des…

The Ranges Library (2) --- C++20

The Ranges Library (2) — C20 比较std与std::ranges算法 比较一下std::sort和std::ranges::sort std::sort template< class RandomIt > constexpr void sort( RandomIt first, RandomIt last );template< class ExecutionPolicy, class RandomIt > void sor…

react(79)--ant design确认框

<Popconfirmplacement"rightBottom"title{text}onConfirm{confirm}okText"Yes"cancelText"No"><Button>RB</Button></Popconfirm>

程序中的得与失

俗话说&#xff0c;舍得&#xff0c;有舍便有得&#xff0c;程序或许和世间万物一个样&#xff0c;讲究阴阳平衡。或许您写程序过程中&#xff0c;得到一颗歪脖树&#xff0c;却放弃了一大片大森林&#xff0c;能正确的取舍矛盾体双方的关系&#xff0c;或许是您扎实功底的体现…