目录
2.4.3 信号量机制
信号量(Semaphores)机制是一种卓有成效的进程同步工具。
1. 整型信号量
把整型信号量定义为一个用于表示资源数目的整型量 S,仅能通过两个标准的原子操作 wait(S) 和 signal(S) 来访问。这两个原子操作又分别被称为 P 和 V 操作,注意必须使用大写。
wait(S) {
while(S <= 0); //do no-op,CPU空转
S--; //访问资源,资源减一
}
signal(S) {
S++; //释放资源,资源加一
}
在 wait(S) 中,对 S 值的测试和做 S=S-1 操作时都是不可中断的。
2. 记录型信号量
整型信号量的问题:忙等,即当 wait(S) 中信号量 S<=0 时,会不停地进行测试,因此未遵循让权等待的原则。而记录型信号量机制则是一种不存在 “忙等” 现象的进程同步机制。
记录型信号量的数据结构可描述如下:
typedef struct {
int value;
struct process_control_block * list;
} semaphore;
原子操作 wait(S) 和 signal(S) 可描述如下:
wait(semaphore * S) {
S->value--;
if(S->value < 0) block(S->list);
//S->value == 0时代表刚好取走最后一个资源
}
signal(semaphore * S) {
S->value++;
if(S->value <= 0) wakeup(S->list);
//S->value == 0时代表刚好还有一个进程被阻塞
}
整型信号量和记录型信号量的问题:
- 只能有效地解决多个进程共享一种临界资源的情况
- 即只需要设置一个临界区
下面来看多个进程需要访问多种临界资源的情况。假设进程 A 和 B 需要访问共享数据 D 和 E,设置互斥型信号量 Dmutex 和 Emutex,两者初始值均为 1,进程 A 和 B 的访问操作如下:
process A: |process B:
wait(Dmutex); |wait(Emutex);
wait(Emutex); |wait(Dmutex);
signal(Emutex); |signal(Dmutex);
signal(Dmutex); |signal(Emutex);
若进程 A 和 B 按下述次序交替执行 wait 操作:
process A: wait(Dmutex); //Dmutex=0
process B: wait(Emutex); //Emutex=0
process A: wait(Emutex); //Emutex=-1, A blocked
process B: wait(Dmutex); //Dmutex=-1, B blocked
进程阻塞以后也不能释放已持有的资源。最后,进程 A 和 B 就将处于僵持状态。在无外力作用下,两者都将无法从僵持状态中解脱出来。我们称此时的进程 A 和 B 已进入死锁状态。
3. AND 型信号量
AND 同步机制的基本思想:将进程在整个运行过程中需要的所有资源,一次性全都地分配给进程,待进程使用完后再一起释放。只要尚有一个资源未能分配给进程,其它所有可能为之分配的资源也不分配给它。
对若干个临界资源的分配采取原子操作:要么把它所请求的资源全部分配到进程,要么一个也不分配。具体方法是在 wait 操作中,增加了一个 “AND” 条件,故称为 AND 同步,或称为同时 wait 操作(simultaneous wait)。
Swait 操作定义如下:
Swait(S1, S2, ... , Sn) {
while(true) {
if(S1 >= 1 && S2 >= 1 && ... && Sn >= 1) {
for(i = 1; i <= n; ++i) Si = Si - 1;
break; //分配完资源后,直接退出while循环
} else {
/*place the process in the waiting queue associated with
the first Si found with Si<1, and set the program count
of this process to the beginning of Swait operation*/
}
}
}
Ssignal 操作定义如下:
Ssignal(S1, S2, ... , Sn) {
for(i = 1; i <= n; ++i) Si = Si + 1;
/*remove all the process waiting in the queue
associated with Si into the ready queue*/
}
4. 信号量集
在每次分配时,采用信号量集来控制,可以分配多个资源。
Swait 操作定义如下:
Swait(S1, t1, d1, ... , Sn, tn, dn) {
while(true) {
if(S1 >= t1 && ... && Sn >= t1) {
for(i = 1; i <= n; ++i) Si = Si - di;
break; //分配完资源后,直接退出while循环
} else {
/*place the process in the waiting queue associated with
the first Si found with Si<ti, and set the program count
of this process to the beginning of Swait operation*/
}
}
}
Ssignal 操作定义如下:
Ssignal(S1, d1, ... , Sn, dn) {
for(i = 1; i <= n; ++i) Si = Si + di;
/*remove all the process waiting in the queue
associated with Si into the ready queue*/
}
三种特例
1)Swait(S, d, d):只有一种信号量 S,允许进程每次申请 d 个资源,当现有资源少于 d 时,不予分配。
2)Swait(S, 1, 1):只有一种信号量 S,允许进程每次申请 1 个资源,当现有资源少于 1 时,不予分配。当 S = 1 时,代表资源总量为 1,从而信号量集退化为互斥信号量;当 S > 1 时,代表资源总量大于 1,从而信号量集退化为记录型信号量。
3)Swait(S, 1, 0):可控开关。当 S >= 1 时,允许多个进程进入某特定区;当 S = 0 时,将阻止任何进程进入特定区。
读写文件举例:允许多个读进程进入临界区,但一旦有一个写进程进入临界区,则将阻止任何进程进入临界区。
P0:Swait(S, 1, 0) //S>=1成立,且不改变信号量
P1:Swait(S, 1, 0) //S>=1成立,且不改变信号量
Pw:Swait(S, 1, 1) //S>=1成立,且S-1=0
P2:Swait(S, 1, 0) //S>=1不成立
2.4.4 信号量的应用
1. 利用信号量实现进程互斥
为使多个进程能互斥地访问某临界资源,只须为该资源设置一互斥信号量 mutex,并设其初始值为 1,然后将各进程访问该资源的临界区 CS 置于 wait(mutex) 和 signal(mutex) 操作之间即可。
- 设置一互斥信号量 mutex
- 设其初始值为 1
- 将临界区 CS 置于 wait(mutex) 和 signal(mutex) 之间
利用信号量实现两个进程互斥的描述如下:
- mutex 互斥
- semaphore 信号量
- 老师用 Pascal 写的,我不能保证我语法正确
var mutex:semaphore:=1; //将mutex定义为semaphore类型,且赋初值为1
begin
parbegin
process1: begin
repeat
wait(mutex);
//critical section
signal(mutex);
//remainder section
until false
end
process2: begin
repeat
wait(mutex);
//critical section
signal(mutex);
//remainder section
until false
end
parend
end
注意:
- 对于互斥型信号量,wait(mutex) 和 signal(mutex) 必须成对出现;
- 缺少 wait(mutex) 导致系统混乱,不能保证对临界资源的互斥访问;
- 缺少 signal(mutex) 会使临界资源永远不被释放,等待该资源的进程不能被唤醒。
2. 利用信号量实现前趋关系
利用信号量来描述程序或语句之间的前趋关系。
其中,Si 是需要执行的语句。
p1(){ S1; signal(a); signal(b); }
p2(){ wait(a); S2; signal(c); signal(d); }
p3(){ wait(b); S3; signal(e); }
p4(){ wait(c); S4; signal(f); }
p5(){ wait(d); S5; signal(g); }
p6(){ wait(e); wait(f); wait(g); S6; }
void main() {
semaphore a, b, c, d, e, f, g;
//将信号量初值均设置为0,若后续进程试图执行则必被阻塞
a.value = b.value = c.value = 0;
d.value = e.value = f.value = g.value = 0;
cobegin //采用并行执行
p1(); p2(); p3(); p4(); p5(); p6();
coend;
}
2.4.5 管程机制
当共享资源用共享数据结构表示时,资源管理程序可用对该数据结构进行操作的一组过程来表示(例如,资源的请求过程 request 和释放过程 release),我们把这样一组相关的数据结构和过程一并称为管程(monitors)。
Hansan 为管程所下的定义是:“一个管程定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据”。
1. 管程的组成
- 管程的名字
- 局部于管程的共享变量说明
- 对该数据结构进行操作的一组过程
- 对局部于管程的数据设置初始值的语句
Monitor(管程)—— 面向对象方法
2. 管程的主要特点
局部数据变量只能被管程的过程访问,任何外部过程都不能访问。
一个进程通过调用管程的一个过程进入管程。
在任何时候,只能有一个进程在管程中执行,调用管程的任何其他进程都被挂起,以等待管程变成可用的。