Semaphore - 信号量
1. 什么是信号量
一个信号量是一个整数变量,在多个进程间进行共享. 信号量的主要目的在于 :
- 处理同步问题
- 并发环境下用于共享资源的访问控制
信号量的取值取决于遇到的具体问题,通常,会使用可用资源的个数作为信号量的初始值。
2. 信号量操作
信号量包含两个独立的原子操作,也就是 wait
和 signal
. 也被称之为 P
(荷兰语Proberen(测试)的首字母) 和 V
荷兰语Verhogen(增加)的首字母.
假设 S
表示一个信号量,他的整数值代表了某种可用资源的数量,那么 wait
和 signal
操作分表可用如下的伪代码表示
wait
如果 S
的值大于 0,wait
操作会简单的进行减 1 操作,表示仍有资源可以用于分配;如果 S
已经是 0 , 表示没有可用的资源进行分配, 那么调用 wait
操作的进程会进入休眠(sleep)状态
signal
如果没有其它进程等待资源, signal
操作会进行一个加 1 操作;否则,一个等待的进程会被唤醒(操作系统调度器),最终,唤醒的进程获得了资源的访问控制权限。
简单总结
wait
: (把信号量减1),若成功,则退出;若失败,则该进程被阻塞;
signal
:(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。
3. 信号量的种类
Binary Semphore - 二元信号量
二元信号量的取值只能是 0 或 1, 可以实现互斥的效果(mutex), 可以用来解决临界区(critial section)问题。
binary semphore is not a mutex ?
Counting Semphore - 计数信号量
计数信号量的取值域是不受限制的,可以用来解决资源分配的同步问题。
信号量的实现,通常需要一个整数值来保存信号量的值,以及一个指针指向等待队列中下一个要执行的进程,同时要避免死锁(deadlock)和饥饿 (starvation)
死锁的情况
进程 0 执行了 wait(S)
操作,然后进程被打断,进程 1 开始执行 wait(Q)
操作后,就开始等待信号量 S
, 会被阻塞,然后进入休眠状态,当内核开始执行进程 0 时,信号量 Q
已经被使用,所以两个进程互相等待对方的情况,出现了死锁
如何解决 ?
4. 使用信号量的例子
临界区问题
临界区(critical section)是指代码的一部分,必须避免并发访问。 这时,可以使用一个二元信号量来解决这个问题,信号量的值初始化为 1. 保证互斥的执行临界区代码,一旦 signal
操作,内核会将休眠的进程添加到就绪队列,等待内核执行该进程时,就可以继续执行临界的代码。确保在任何时刻,临界区的代码只有一个和进程在执行
以顺序执行
假设要执行代码片段 S1 和 S2 ,但是要求 S1 先执行, S2 后执行
使用一个初始值为 0 的信号量就可以解决这个问题:
生产者/消费者问题
在这个问题中,生产者往缓冲区中生产物品,消费中从缓冲区中拿出来消费,他们分别对应 Producer 进程和 Consumer 进程,这是,使用一个信号量 S
代表换取中的物品个数,初始化为 0.
缓冲区满了,生产者应该停止生产,进入休眠状态,缓冲区空闲了,谁来通知 Producer 进程呢?
使用三个信号量:
empty
: 表示缓冲区中剩余空间大小,初始化为缓冲区的大小
full
:表示缓冲区中已用空间大小,初始化为 0
S
用来保护缓冲区同时只能被一个进程访问, 初始化 1
·
生产者:
如果 empty
信号量变为 0 ,生产者进入休眠状态;否则往缓冲区里进行生产,然后通知 full
信号量,因此会触发唤醒任何可能等待的消费者
消费者:
如果 full
信号量变为 0 ,消费者进入休眠状态,否则从缓冲区中取出物品,并通知 empty
信号量,因此会触发唤醒任何需要进程生成的生成者
除非缓冲区中还有空间,否则生产者进入休眠状态;除非缓冲区中还有元素,否则消费者进入休眠状态
还有很多案例,包括哲学家吃饭问题,同一数据集上的读写问题….
工作中的例子
参考文献
[2] 哲学家进餐问题
[3] PV操作