在整型信号量中,如果某个进程一直等待,不妨把这个进程放入阻塞队列,为CPU腾出空间,当该进程条件满足再将其从阻塞队列中拿出。因此记录资源的类型发生了变化:
typedef struct{
int value;
struct process_control_block * list;
} semaphore;
value表示资源数量,list表示该类资源的阻塞队列。wait和signal也发生了变化:
wait(semaphore *S)
{
S->value--;
if (S->value<0)
block(S->list);
}
signal(semaphore *S)
{
S->value++;
if (S->value<=0)
wakeup(S->list);
}
整体的互斥模式结构不变:
semaphore mutex = 1;
begin
parbegin
process 1: begin
repeat
wait(mutex);
critical section
signal(mutex);
remainder section
until false;
end
process 2: begin
repeat
wait(mutex);
critical section
signal(mutex);
remainder section
until false;
end
parend
end
改变后的代码需要重新梳理逻辑,假设现在mutex=1,P1执行wait操作,value--表示资源使用,当前资源mutex=0,那么接下来的判断value<0不成立,不需要将进程放入阻塞队列。与此同时P2也执行了wait操作,value--,那么mutex=-1表示资源不足,if判断通过,P2被放入阻塞队列。
P1执行完临界区代码执行signal操作,value++表示资源回收,当前mutex=0,满足if条件,从阻塞队列中拿出进程P2。接下来P2进程由于已经执行了wait操作开始继续执行临界区代码,然后signal操作回收资源,最后mutex=1。
细心观察可以发现一个规律:信号量value>0,value表示可用资源的数量;value<0,value表示因该类资源阻塞的进程数量。
这里两个if判断容易混淆,记不清哪个有等于号,其实这只是一个逻辑,wait和signal操作还可以这样写:
wait(semaphore *S)
{
if (S->value<=0)
block(S->list);
S->value--;
}
signal(semaphore *S)
{
if (S->value<0)
wakeup(S->list);
S->value++;
}
先判断,那么等于号就在wait这里;后判断,等于号就在signal那里。
记录型信号量也是有缺点的,比如现在的情景是两个进程A和B,共享数据D和E,为其分别设置互斥信号量Dmutex和Emutex,初值均为1。使用记录型信号量要保证wait和signal成对出现使用,于是代码如下:
Process A:
wait(Dmutex);
wait(Emutex);
// 使用D、E
Signal(Dmutex)
Signal(Emutex)
Process B:
wait(Emutex);
wait(Dmutex);
// 使用D、E
Signal(Dmutex)
Signal(Emutex)
假如进程A执行wait(Dmutex)操作,Dmutex--;同时进程B执行wait(Emutex)操作,Emutex--;那么A进程再执行wait(Emutex)操作时就会使进程A进入阻塞状态;同样的,B进程接下来执行wait(Dmutex)操作也会让进程B进入阻塞队列。两个进程都进入阻塞队列,就成了死锁现象。所以共享的资源越多,死锁的可能性越大。
为了解决记录型信号量中的死锁问题,出现了AND型信号量。
传送门:AND型信号量