读书笔记
🚔《Operating System:Three Easy Pieces》第三十章 条件变量
00 分钟
2023-1-17
2023-4-15
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》第二十九章 基于锁的并发数据结构

评论
Loading...