文章目录
信号量机制是Dijkstra提出的一种卓有成效的进程同步工具。信号量有整形信号量、记录型信号量、AND型信号量等,这里主要介绍我们常见的记录型信号量。
1. 基本结构
typedef struct {
int value; //信号量值
struct process_cntrol_block *list; //阻塞队列
}semaphore;
在应用信号量的时候,信号量的值往往是临界资源的数量。当临界资源数量不足时,新的进程就阻塞,并插入到信号量阻塞队列中。
2. P,V操作
wait(S)和signal(S)操作是信号量机制的基本操作(通常称作P,V操作),定义如下:
wait(semaphore *S) {
S->value--; //信号量的值减一
if(S->value < 0) block(S->list); //如果信号量的值小于零,进程阻塞
signal(semaphore *S) {
S->value++; //信号量的值加一
if(S->value <= 0) wakeup(S->list); //唤醒
}
在实现进程同步时,一般先将S->value的初值设定为临界资源的初始数量,一旦进程要请求一个临界资源,先对代表此进程的信号量执行P操作,也就意味着资源数减一,当该进程运行完毕时,执行V操作,意味着资源数加一。值得注意的是,如果当前资源数量为0,再有进程想请求临界资源,就会在执行P操作时进入阻塞队列,信号量值变为-1,此后执行P操作的进程也都会被阻塞,信号量的绝对值为阻塞的进程数目。
3. 信号量的应用
3.1 信号量实现进程互斥
有了P,V操作,我们就能很简单的实现进程互斥。方法是:设mutex为互斥信号量,初值为1。在需要互斥的临界区前后分别使用P,V操作即可,示意代码如下:
semaphore mutex = 1; //一般初值为1的信号量用来实现互斥
P_A() {
//进程A
while(1) {
P(mutex);
临界区;
V(mutex);
剩余区;
}
}
P_B() {
//进程B
while(1) {
P(mutex);
临界区;
V(mutex);
剩余区;
}
}
在互斥问题中,我们可以把P操作理解成“上锁”,把V操作理解成“解锁”,当P_A和P_B两个进程并发执行时,无论哪一个进程先执行到了P操作,都会接下来执行P操作的进程阻塞,直到前一个进程执行到了V操作为止,这样就实现了临界区的互斥。
3.2 信号量实现前驱关系
思路如下:为每一组想要实现前后关系的进程,都分别定义一个信号量,初值设为0,把你想要先执行的进程后面加一个V操作,想要后执行的进程前面加一个P操作。这样一来,你想要后执行的进程如果先执行了,就会因为执行了其前面的P操作而阻塞,直到你想要先执行的进程执行完了,执行V操作后,才能解除阻塞继续执行。以上就是用信号量实现前驱关系的过程。
假如我们要实现四个语句S1,S2,S3,S4需要按照一定的顺序同步执行,比如S1执行完S2,S3才能执行,S2,S3执行完了S4才能执行,代码如下:
p1() {
S1; V(a); V(b);}
p2() {
P(a); S2; V(c