并发程序设计之信号量与互斥锁——学习笔记
一、信号量是什么
信号量(semaphore)是操作系统用来解决并发中的互斥和同步问题的一种方法。 信号量是一个与队列有关的整型变量,其数据结构为一个值和一个指针,指针指向等待该信号量的下一个进程。你可以把它想象成一个数后面拖着一条排队的队列,那信号量拖着的那个队列就是用来放正在排队想要使用这一资源的进程,如图:
那信号量的值S代表什么意思呢?信号量的值与相应资源的使用情况有关:
S>0:当前有可用资源,表示当前可用资源的数量为S;
S=0:资源都被占用,表示当前可用资源的数量为0;
S<0:资源都被占用,其绝对值表示等待使用该资源的进程个数,即还有S个进程正在排队
注意,信号量的值仅能由信号量机制来改变,即利用pv操作来对信号量进行处理。
一般来说,信号量S>=0时,S表示可用资源的数量。执行一次P操作意味着请求分配一个单位资源,因此S的值减1;当S<0时,表示已经没有可用资源,请求者必须等待别的进程释放该类资源,它才能运行下去。而执行一个V操作意味着释放一个单位资源,因此S的值加1;若S<=0时,表示有某些进程正在等待该资源,因此要唤醒一个等待状态的进程,使之运行下去。
二、信号量伪代码
1 struct semaphore{ 2 int count; 3 queueType queue; 4 }; 5 6 void semWait(semaphore s){ 7 s.count--; 8 if(s.count < 0){ 9 /*place this process in s.queue*/; 10 /*block this process*/; 11 } 12 } 13 14 void semSignal(semaphore s){ 15 s.count++; 16 if(s.count <= 0){ 17 /*remove a process P from s.queue*/; 18 /*place a process P on ready list*/; 19 } 20 }
在代码中我们可以看到有两个对信号量的count值和阻塞队列的操作,一个是semWait,一个是semSignal,前者也被称为P操作,后者也被称为V操作。
semWait:p操作(wait):申请一个单位资源,进程进入;
经典伪代码:
1 wait(S){ 2 while(s<=0);//如果没有资源则会循环等待; 3 S-- ; 4 }
semSignal:v操作(signal):释放一个单位资源,进程出来;
经典伪代码:
1 signal(S){ 2 S++ ; 3 }
当申请资源的时候,资源数count值--,我们注意到资源数,如果在--之后<0,那么这个这个进程就会加入到等待队列。
为什么这个条件设置成<0呢?其实很好理解,当这个资源已经其他进程占有完了,即为0或者负数,那么新进程要申请这个资源时资源数再减1必然count<0,那么这个进程就要被被阻塞,进入阻塞队列。
再看semSignal操作
一个进程终会使用完这个进程,然后离开,那么此时可用资源数++
为什么这个条件设置成<=0呢?一个进程用完资源走了,count++,如果还有进程在排队(count即值是-1或者更小),那++之后必然count<=0,此时就唤醒一个排队中的进程。
PV操作的意义 :我们用信号量及PV操作来实现进程的同步和互斥。PV操作属于进程的低级通信。
使用PV操作实现进程互斥时应该注意的是:
(1)每个程序中用户实现互斥的P、V操作必须成对出现,先做P操作,进临界区,后做V操作,出临界区。若有多个分支,要认真检查其成对性。
(2)P、V操作应分别紧靠临界区的头尾部,临界区的代码应尽可能短,不能有死循环。
(3)互斥信号量的初值一般为1。
三、总结
semWait(S):请求分配一个资源。
semSignal(S):释放一个资源。
semWait、semSignal操作必须成对出现。
多个semWait操作的次序不能颠倒,否则可能导致死锁。
多个semSignal操作的次序可任意。
用于互斥时,位于同一进程内;用于同步时,交错出现于两个合作进程内。且在前事件后加semSignal,在后事件前加semWait,比如先刷牙再吃饭,那刷牙这个事件后加semSignal,在吃饭这个事件前加semWait
互斥关系:缓冲区是临界资源,各进程互斥访问