条件变量基本概念和原理

简介:

条件变量的作用是用于多线程之间关于共享数据状态变化的通信。当一个动作需要另外一个动作完成时才能进行,即:当一个线程的行为依赖于另外一个线程对共享数据状态的改变时,这时候就可以使用条件变量

假设没有条件变量,对于一个生产者消费者问题,消费线程在得知队列中没有产品时,将阻塞自己。生产线程给队列中放入产品,但是没有办法激活消费线程,而消费线程处于阻塞状态也没有办法自激活。如果消费线程使用忙等的方式,通过不断地查询来判断是否有产品将大量的浪费 CPU 时间。消费线程可以使用睡眠 + 查询的方式,即发现队列中没有产品时, sleep 一段时间,然后再查询。问题是睡眠多长时间?时间太长,实时性不好,时间太短,还是浪费 CPU 时间。

所以,通过生产线程通过唤醒消费线程时最好的方式。现在我们考虑一种实现,消费线程在阻塞之前要先解锁,同时还要将自己的标识符放入一个地方,以便生产线程通过这个标识符来激活自己。这样看起来是没问题了,然而不要忘记了,线程之间是并发/并行的。消费线程可能刚完成解锁的操作,就被生产线程获取到了并开始执行,这时,因为消费线程还未挂起自己,来不及将自己的标识符保存在某个位置,所以生产线程不认为有正在等待的线程。这时,切换到消费线程后,消费线程将永远的等待下去,虽然队列中有产品。而生产线程因为队列中有产品可能也一直的等待下去,形成了死锁。

解决方法是必须让解锁、保存线程标识符、挂起这一系列操作成为原子操作。这中解决方案就是条件变量,所以不难想到使用条件变量的时候必须要“伴随”一个互斥量。

条件变量是与互斥量相关联的一种用于多线程之间关于共享数据状态改变的通信机制。它将解锁和挂起封装成为原子操作。等待一个条件变量时,会解开与该条件变量相关的锁,因此,使用条件变量等待的前提之一就是保证互斥量加锁。线程醒来之后,该互斥量会被自动加锁,所以,在完成相关操作之后需要解锁。

条件变量总是和谓语相关,学过《离散数学》应该还记得谓语表达式吧。谓语是具有唯一真假值的句子。程序中,可以用谓语来描述当前线程需要的状态。如果该谓语值为假,需要使用条件变量等待。醒来之后,由于系统的并发性,一般需要再次判断谓语值是否为真,如果不为真,则再次使用条件变量进行等待。

互斥量是用来防止对不变量的破坏,换句话说,是用来规范线程对共享数据的竞争使用。而条件变量是用来对线程同步,即用来协调各个线程合作完成某个任务。比如:足球场上,两个足球队对一个球的使用叫做竞争,可以使用“马赛回旋”这种“互斥量”对球加锁,防止被抢。而传球这个动作就是使用“条件变量”进行唤醒,它的作用是保证一个球队的各个成员能协作起来将球踢进对方的球门。

条件变量的关注对象是共享数据状态的变化,这一变化可以使用谓语来描述。因为涉及到共享数据,所以需要互斥量。互斥量和条件变量的对应关系为1:N.就是说一个互斥量可以对应多个条件变量,一个条件变量只能对应一个互斥量。这个可以这样理解:因为共享数据有很多种状态,描述这些状态就需要多个谓语,所以需要用多个条件变量。

条件变量和谓语的对应关系一般最好为1:1.1N或者N1并不是不可以,但是容易引起死锁和竞争问题,要特别注意。若1个条件变量对应于多个谓语时,唤醒应采用广播的方式而不是signal的方式。



本文转自hipercomer 51CTO博客,原文链接:http://blog.51cto.com/hipercomer/914841


相关文章
|
存储 安全 对象存储
oss访问控制(Access Control)
oss访问控制(Access Control)
1298 4
|
存储 Shell Linux
Docker(24)- docker login 命令详解
Docker(24)- docker login 命令详解
2032 0
Docker(24)- docker login 命令详解
|
移动开发 Dart 前端开发
从架构到源码:一文了解Flutter渲染机制
Flutter从本质上来讲还是一个UI框架,它解决的是一套代码在多端渲染的问题。在渲染管线的设计上更加精简,加上自建渲染引擎,相比ReactNative、Weex以及WebView等方案,具有更好的性能体验。本文将从架构和源码的角度详细分析Flutter渲染机制的设计与实现。较长,同学们可收藏后再看。
8376 1
从架构到源码:一文了解Flutter渲染机制
|
域名解析 缓存 网络协议
DNS协议 是什么?说说DNS 完整的查询过程? _
DNS是互联网的域名系统,它像翻译官一样将域名转换成IP地址。域名由点分隔的名字组成,如www.xxx.com,包含三级、二级和顶级域名。查询方式分为递归和迭代,递归是请求者必须得到答案,而迭代则是服务器指引请求者如何获取答案。域名解析过程中,会利用浏览器和操作系统的缓存,如果缓存未命中,本地域名服务器会通过递归或迭代方式向上级服务器查询,最终得到IP地址并返回给浏览器,同时在各级缓存中保存记录。
703 1
DNS协议 是什么?说说DNS 完整的查询过程? _
|
存储 算法 数据处理
C++一分钟之-范围基础:views与ranges
【7月更文挑战第1天】C++20的Ranges库简化了集合操作,引入了Range(具有begin()和end()的对象)和View(延迟计算的Range)。常见问题包括混淆Range与Container、忽视View的延迟性和错误修改只读View。要避免错误,需理解Range概念、明确操作执行时机并检查View的可变性。代码示例展示了如何过滤并平方vector中的奇数,体现Range的使用。范围库带来了代码的简洁和效率,但理解其工作原理至关重要。
403 0
|
设计模式 算法 编译器
【C/C++ PIMPL模式 】 深入探索C++中的PIMPL模式
【C/C++ PIMPL模式 】 深入探索C++中的PIMPL模式
980 0
|
存储 编译器 API
BackTrader 中文文档(十)(1)
BackTrader 中文文档(十)
622 0
|
存储 数据可视化 关系型数据库
绘制圆环图/雷达图/星形图/极坐标图/径向图POLAR CHART可视化分析汽车性能数据
绘制圆环图/雷达图/星形图/极坐标图/径向图POLAR CHART可视化分析汽车性能数据
|
算法 Linux 调度
C++ std::condition_variable 条件变量类探索:解锁条件变量的底层原理
C++ std::condition_variable 条件变量类探索:解锁条件变量的底层原理
1173 0
|
JavaScript
汇编指令学习(JMP、JE、JS、JP,JO,JB)
汇编指令学习(JMP、JE、JS、JP,JO,JB)
1259 0

热门文章

最新文章