Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >杂记随笔:唤醒丢失问题 & 条件变量 vs 信号量

杂记随笔:唤醒丢失问题 & 条件变量 vs 信号量

作者头像
Miigon
发布于 2022-10-27 07:58:53
发布于 2022-10-27 07:58:53
71800
代码可运行
举报
文章被收录于专栏:Miigon's BlogMiigon's Blog
运行总次数:0
代码可运行

Scenario

考虑 producer-consumer 同步模式中的 receiver:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
receive(bb):
	acquire(bb.lock)
	while bb.out >= bb.in:
		release(bb.lock)		# release lock before sleep
								# so other threads can run
		wait(bb.has_message) 	# wait on conditional variable
		acquire(bb.lock)		# reacquire
	message <- bb.buf[bb.out mod N]
	bb.out <= bb.out + 1
	release(bb.lock)
	notify(bb.has_space)
	return message

在没有新消息进入的时候,receiver 应该放弃共享缓冲区的锁,然后进入睡眠等待 sender 唤醒。 然而上述代码的问题在于,「放弃缓冲区锁」和「进入睡眠」不是一步原子操作,而是独立的两步操作。

Problem

在 receiver 放弃共享缓冲区锁(release(bb.lock))之后,但是在进入睡眠(wait(bb.has_message) )之前,另一个 sender 有可能在这个间隙中发送消息。

在 receiver 进入睡眠之前,sender 会看到没有接收者正在等待 has_message,于是该 receiver 并不会得到消息通知。sender 发送完成后,receiver 才进入睡眠,最好的情况下需要等待下一次 sender 唤醒才能被唤醒,最差情况下永远都不会被唤醒。(deadlock)

同样在 receive 从睡眠中唤醒之后以及重新获取锁之前,并发的 sender 也同样可能发送消息,这一部分消息的通知也无法被 receiver 收到。

该问题被称为 “lost wakeup problem” 或 “lost notify problem”,可译为“唤醒丢失”

lost wakeup 检测方法:if(receiver 收到通知的次数 < sender 发送通知的次数)

Solution: atomic release-wait-reacquire

问题出在 release(bb.lock)wait(bb.has_message) 之间留出来的短暂间隙。解决方法是由操作系统提供一个「原子性释放锁与进入等待」机制,以及「唤醒后原子性重新获得锁」的机制(release-wait-acquire)。

也就是,将这三行代码合成一个原子操作:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
release(bb.lock)		# release lock before sleep
						# so other threads can run
wait(bb.has_message) 	# wait on conditional variable
acquire(bb.lock)		# reacquire

monitor (condition variable)

在 POSIX 上,这个机制为 pthread condition variable,包含如下操作:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# atomic [release mutex `m`, sleep on `c`, reacquire mutex after wakeup]
pthread_cond_wait(c, m)

pthread_cond_signal(c)		# wake up 1 thread sleeping on `c`
pthread_cond_broadcast(c)	# wake up all threads sleeping on `c`

pthread_cond_wait 提供了原子性的「释放互斥锁—进入睡眠—在唤醒后重新获得锁」操作。

即改为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
receive(bb):
	acquire(bb.lock);
	while bb.out >= bb.in:
		pthread_cond_wait(bb.has_message, bb.lock)
	message <- bb.buf[bb.out mod N]
	bb.out <= bb.out + 1
	release(bb.lock)
	pthread_cond_signal(bb.has_space)
	return message

semaphore

另一个解决思路是使用信号量 semaphore。只适用于能够抽象为「某个整数是否大于 0」的 condition(例如剩余缓冲区数量、剩余消息数量)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sem_wait(s)
sem_post(s)

检查是否需要 sleep 的逻辑,和进入睡眠的逻辑一起被包含在了 P 原子操作中(相比于 monitor/condition variable 的由用户程序自己做检测)。

P 原子操作包含了整个「获得互斥锁—判断资源数量—释放互斥锁—进入睡眠—在唤醒后重新获得锁」的过程,所以我们所需要的「释放互斥锁—进入睡眠—在唤醒后重新获得锁」过程自然也是原子性的。

differences between the two

可以将 semaphore 看作「condition 为某个整数 > 0 的 condition variable」

semaphore 适合在等待条件可以用一个整数描述的时候使用。条件变量的维护工作由 P(wait)、V(signal) 原子操作完成。

condition variable 则将判断等待条件的任务交给了用户程序,提供了更大的自由度和灵活性。可以用来等待一些不可以用「整数>0」描述的条件变量,例如网络事件和同步屏障(需要等待整数 = 0 ,信号量为等待整数 > 0)(s081-lab7-multithreading-barrier)。

小细节:

  • 对于 semaphore 来说,signal 操作在没有进程正在等待的时候,并不会丢失,而是会被记录为整数+1
  • 对于 condition variable,signal 操作在没有进程正在等待的时候,会丢失。
  • semaphore 使用了内部的互斥锁保证原子性,condition variable 使用了外部传入的互斥锁保证原子性

可以使用「维护一个整数 i + 等待「i > 0」的 condition variable」来实现 semaphore

References:

  • https://en.wikipedia.org/wiki/Monitor_(synchronization)#Condition_variables
  • https://en.wikipedia.org/wiki/Producer–consumer_problem
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
信号量和管程
我们进入信号区可以如果是读操作, 那么就可以允许它几个信号同时进行, 如果是写操作 ,那么就设置只能是一个信号进行。
用户11097514
2024/05/30
1510
信号量和管程
多线程锁有几种类型_进程同步和互斥概念
现代操作系统基本都是多任务操作系统,即同时有大量可调度实体在运行。在多任务操作系统中,同时运行的多个任务可能:
全栈程序员站长
2022/09/22
1.3K0
多线程锁有几种类型_进程同步和互斥概念
UNPv2第七章:互斥锁与条件变量
 pthread_mutex_lock()函数是一个阻塞型的上锁函数,若互斥锁已经上了锁,调用pthread_mutex_lock()函数对互斥锁再次上锁的话,调用线程会阻塞,直到当前互斥锁被解锁。  pthread_mutex_trylock()函数是一个非阻塞型的上锁函数,如果互斥锁没被锁住,pthread_mutex_trylock()函数将把互斥锁加锁, 并获得对共享资源的访问权限;如果互斥锁被锁住了,pthread_mutex_trylock()函数将不会阻塞等待而直接返回EBUSY(已加锁错误),表示共享资源处于繁忙状态。  如果互斥锁变量mutex已经上锁,调用pthread_mutex_unlock()函数将解除这个锁定,否则直接返回。该函数唯一的参数mutex是pthread_mutex_t数据类型的指针。该函数调用成功返回0,否则返回-1。
提莫队长
2019/02/21
9100
信号量(semaphore)
信号量也是一种锁,相对于自旋锁,当资源不可用的时候,它会使进程挂起,进入睡眠。而自旋锁则是让等待者忙等。这意味着在使用自旋锁获得某一信号量的进程会出现对处理器拥有权的丧失,也即时进程切换出处理器。信号量一般用于进程上下文,自旋锁一般用于中断上下文。
DragonKingZhu
2020/03/24
8900
Unsafe类park和unpark方法源码深入分析(mutex+cond)
说明:本篇博客整理自文末的多篇参考博客(每篇博客各有侧重)。本文结合源码对Unsafe的park和unpark方法进行了完整全面的梳理,并对部分参考博客中存在的错误描述进行说明。
saintyyu
2021/11/22
4840
Unsafe类park和unpark方法源码深入分析(mutex+cond)
线程同步-条件变量
有一个非常好的VIP自习室,一次只允许一个人进来,每一个自习完成的同学归还钥匙后,不能立马申请,第二次申请必须排队,也就是说其他人也必须排队,这种去自习室自习有一定的顺序性,这称之为同步。
南桥
2024/08/24
1120
线程同步-条件变量
System|Concurrency|条件变量
有界缓冲区问题,sender向buffer中添加数据,receiver从buffer中取出数据。以两个索引in,out作为未读取数据的上下边界,buf作为存储未读取数据的缓冲区。
朝闻君
2021/11/22
5650
System|Concurrency|条件变量
Linux同步机制(二) - 条件变量,信号量,文件锁,栅栏
1 条件变量 条件变量是一种同步机制,允许线程挂起,直到共享数据上的某些条件得到满足。 1.1 相关函数  #include <pthread.h>  pthread_cond_t cond = PTHREAD_COND_INITIALIZER;  int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t*cond_attr);  int pthread_cond_signal(pthread_cond_t *cond);  int
三丰SanFeng
2018/01/16
3K0
信号量与管程以及原子性,pv原语操作,临界资源和临界区,同步和互斥,信号量,管程与临界区不同,信号量和互斥锁的区别,互斥量(Mutex)
程序的原子性指:整个程序中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。
zhangjiqun
2024/12/16
1740
信号量与管程以及原子性,pv原语操作,临界资源和临界区,同步和互斥,信号量,管程与临界区不同,信号量和互斥锁的区别,互斥量(Mutex)
详解Linux多线程中互斥锁、读写锁、自旋锁、条件变量、信号量
---- Hello、Hello大家好,我是木荣,今天我们继续来聊一聊Linux中多线程编程中的重要知识点,详细谈谈多线程中同步和互斥机制。 同步和互斥 互斥:多线程中互斥是指多个线程访问同一资源时同时只允许一个线程对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的; 同步:多线程同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
Linux兵工厂
2023/02/28
3.8K0
详解Linux多线程中互斥锁、读写锁、自旋锁、条件变量、信号量
Linux线程编程同步之互斥锁和条件变量
今天是最后一篇关于Linux线程编程的文章分享,在这里我们先掌握基础的概念及其应用,后面在慢慢去深入学习。最近看到一句说的非常在理:理论’是你知道是这样,但它却不好用。‘实践’是它很好用,但你不知道是为什么。我想大多数学习者,和我一样,在学习的过程中,都会或多或少的有这种情况,不过自己坚信,你把基础打好(同时学的过程中,不要好高骛远,三心二意的,把自己先暂时用到的东西学明白,再去学其他东西,不要当前的,没学会,又跑去学其他的,而且又学不会,这样浪费时间和精力;这个这里基础打好,举个例子,你的c语言功底要打好,对指针的使用非常熟悉,甚至一些高级用法就是要平时慢慢积累和总结,以及内存原理要知道为什么是这样等方面),后面实战的话,就好多了,至少不会说我这个东西不会那个东西又不会,这样会让自己很痛苦当初为啥没学好基础,现在实战中漏洞百出。好了,废话不多说了,开始下面的主题分享:
用户6280468
2022/03/21
1.7K0
Linux线程编程同步之互斥锁和条件变量
编程小知识之 虚假唤醒(spurious wakeup)
高层次的多线程编程中,条件变量是个常见的同步方法,跟传统仅使用互斥量的方法相比,条件变量可以减少锁的竞争.
用户2615200
2019/12/20
2.1K0
【线程同步】条件变量
条件变量不是锁,它经常和互斥量组合使用。以生产者消费者模型为例,当前有多个消费者线程竞争一个资源,当资源为空时,消费者线程会阻塞在一个条件上,等待生产者通知,生产者写数据到临界区并通知消费者,此时消费者去竞争这个资源并读取数据。它是这样实现的,第一个线程访问资源的时候,获得互斥锁,调用pthread_cond_wait将会释放锁,并阻塞在条件cond上面,这是第二个线程到来,依然可以获得互斥锁,然后这个线程如果调用pthread_cond_wait也会会释放锁,并阻塞在条件cond上面,这样,所有线程就都阻塞在cond上面。
mindtechnist
2024/08/08
1280
【线程同步】条件变量
linux网络编程之posix 线程(四):posix 条件变量与互斥锁 示例生产者--消费者问题
s1mba
2017/12/28
1.4K0
linux网络编程之posix 线程(四):posix 条件变量与互斥锁 示例生产者--消费者问题
线程同步之条件变量(pthread_cond_wait)
条件变量给了线程以无竞争的方式等待特定条件发生。条件变量是和互斥量一起使用的,条件变量是由互斥量保护的。这么讲,大家可能不明白,这条件变量有什么用?干什么的?还是结合pthread_cond_wait()函数来分析一下吧!
zy010101
2020/05/18
19.1K2
线程同步之条件变量(pthread_cond_wait)
【Linux】:多线程(互斥 && 同步)
寄存器不等于寄存器的内容,线程在执行的时候,将共享数据,加载到 CPU 寄存器的本质:把数据的内容,变成了自己的上下文,同时自己拷贝了一份数据
IsLand1314
2024/12/20
1180
【Linux】:多线程(互斥 && 同步)
多线程安全-iOS开发注意咯!!!
但是并不是非常完美,因为多线程常常伴有资源抢夺的问题,作为一个高级开发人员并发编程那是必须要的,同时解决线程安全也成了我们必须要要掌握的基础
iOSSir
2019/05/08
8910
从软件(Java/hotspot/Linux)到硬件(硬件架构)分析互斥操作的本质
一切互斥操作的依赖是 自旋锁(spin_lock),互斥量(semaphore)等其他需要队列的实现均需要自选锁保证临界区互斥访问。
执生
2021/01/30
8810
操作系统:第二章 进程的描述与控制(下)
进程同步:在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系。
Here_SDUT
2022/08/08
6840
操作系统:第二章 进程的描述与控制(下)
如何理解互斥锁、条件变量、读写锁以及自旋锁?
锁是一个常见的同步概念,我们都听说过加锁(lock)或者解锁(unlock),当然学术一点的说法是获取(acquire)和释放(release)。
果冻虾仁
2021/12/08
1.6K0
如何理解互斥锁、条件变量、读写锁以及自旋锁?
推荐阅读
相关推荐
信号量和管程
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验