信号量
本章为进阶内容,零基础读者可以跳过,不影响后续阅读。
复习
- 自旋锁与睡眠锁:等待锁时应该原地重试还是让出 CPU
- 等待与通知:条件变量如何让线程在条件不满足时睡眠
- 生产者与消费者:用互斥和等待通知串起一个完整例子
TL;DR
- 信号量(semaphore)是一个带计数的同步工具
P操作把计数减一,若为负就等待;V操作把计数加一,并唤醒等待者- 计数为 1 时,它可以当互斥锁用
- 计数大于 1 时,它可以管理“有 N 份的有限资源”
正文
上一章用“锁 + 条件变量”实现了生产者与消费者。其实,还有一件更“一揽子”的工具,能把互斥和等待都装进去——信号量。
一个计数加一个队列
信号量的核心,其实只有一个整数和一个等待队列:
- 一个计数,表示“现在还剩多少份资源”
- 一个等待队列,存着暂时拿不到资源的线程
它只对外暴露两个动作,习惯上叫 P 和 V:
P(等待):把计数减一。如果减完小于 0,说明资源已经被占光,当前线程进入等待队列睡下。V(发信号):把计数加一。如果还有线程在等,就唤醒其中一个。
一减一加之间,信号量就把“抢资源、排队、通知”全包了。
当锁用:计数为 1
如果把计数初始化成 1,信号量就退化成一个互斥锁:
P:计数从 1 减到 0,成功进入;再来一个就减到 -1,只能等V:计数加回去,唤醒一个等待者
所以,互斥锁可以看作信号量在“计数为 1”时的特例,这种用法叫二元信号量。
管理多份资源:计数大于 1
信号量真正比锁强的地方,是计数可以大于 1。
比如系统里有 3 台打印机,就把信号量初始化成 3:每台被占用时 P 一下,用完 V 一下。第 4 个来申请的线程会被挡住,直到有人还回来。这样,一行计数就替我们管住了“有限资源”。
用信号量重写生产者与消费者也很简洁:用两个信号量分别表示“空位数量”和“数据数量”,生产者 P 空位、V 数据,消费者反过来。计数器天然表达了缓冲区里还有多少空位、多少数据,省去了手写条件判断。
它与条件变量的关系
信号量和条件变量,解决的都是“等待与唤醒”。区别在于:
- 条件变量只负责等和叫,条件本身要自己用变量判断
- 信号量自带计数,条件就是“还有没有资源”
可以说,信号量是一种“已经打包好了条件”的同步原语,用起来更省事,但灵活性稍低。
思考题
把信号量的计数初始化成 3,三个线程各自
P一次后,第四个线程来P会怎样?它什么时候才能等到?
小结
知识点
- 信号量 = 计数 + 等待队列
P减一并可能等待,V加一并可能唤醒- 计数为 1 时当互斥锁;计数大于 1 时管理有限资源
- 生产者与消费者也可用信号量实现
参考资料
- Wikipedia(zh):信号量:semaphore
- Wikipedia(zh):互斥锁:二元信号量
思考题答案(仅供参考)
第四个线程 P 之后,计数从 0 变成 -1,说明资源已经用完,它会进入信号量的等待队列睡下。直到前面三个线程中有任意一个执行 V,计数回升、等待者被唤醒,它才有机会拿到资源继续运行。这正是信号量“计数管理有限资源”的用法:超过可用份数的申请,自动排队。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪