type
status
date
slug
summary
tags
category
icon
password
Property
Apr 15, 2023 03:23 PM
锁并不是并发程序设计所需的唯一原语。在很多情况下,线程需要检查某一
条件
(condition)满足之后,才会继续运行。尝试用一个
共享变量
。这种解决方案一般能工作,但是效率低下,因为主线程会自旋检查
,浪费 CPU 时间。我们希望有某种方式让父线程休眠,直到等待的条件满足(即子线程完成执行)。定义和程序
线程可以使用
条件变量
(condition variable),来等待一个条件变成真。条件变量是一个
显式队列
,当某些执行状态(即条件,condition)不满足时,线程可以把自己加入队列,等待(waiting)该条件。另外某个线程,当它改变了上述状态时,就可以唤醒一个或者多个等待线程(通过在该条件上发信号),让它们继续执行。要声明这样的条件变量,只要像这样写:
pthread_cond_t c
;,这里声明 c 是一个条件变量(注意:还需要适当的初始化)。条件变量有两种相关操作:wait()和 signal()。线程要睡眠的时候,调用 wait()。当线程想唤醒等待在某个条件变量上的睡眠线程时,调用 signal()。wait()调用有一个
参数
,它是互斥量
。它假定在 wait()调用时,这个互斥量是
已上锁
状态。wait()的职责是释放锁
,并让调用线程休眠
(原子地)。当线程被唤醒
时(在另外某个线程发信号给它后),它必须重新获取锁
,再返回调用者。提示:发信号时总是持有锁尽管并不是所有情况下都严格需要,但有效且简单的做法,还是在使用条件变量发送信号时持有锁。虽然上面的例子是必须加锁的情况,但也有一些情况可以不加锁,而这可能是你应该避免的。因此,为了简单,请在调用 signal
时持有锁
(hold the lock when calling signal)。这个提示的反面,即调用 wait 时持有锁,不只是建议,而是 wait 的语义强制要求的。因为 wait 调用总是假设你调用它时已经持有锁、调用者睡眠之前会释放锁以及返回前重新持有锁。因此,这个提示的一般化形式是正确的:调用 signal 和 wait 时要持有锁(hold the lock when calling signal or wait),你会保持身心健康的。
生产者/消费者(有界缓冲区)问题
最终代码:
覆盖条件
以下代码用于内存分配管理,free 后会唤醒 allocate 时因空间不够而等待的线程
用
pthread_cond_broadcast()
代替上述代码中的pthread_cond_signal(),唤醒所有的等待线程。这样做,确保了所有应该唤醒的线程都被唤醒。当然,不利的一面是可能会影响性能,因为不必要地唤醒了其他许多等待的线程,它们本来(还)不应该被唤醒。这些线程被唤醒后,重新检查条件,马上再次睡眠。
参考
上一篇
《Operating System:Three Easy Pieces》第三十一章 信号量
下一篇
《Operating System:Three Easy Pieces》第二十九章 基于锁的并发数据结构
- 作者:GJJ
- 链接:https://blog.gaojj.cn/article/blog-56
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。