引入进程后,虽然使系统中的多道程序并发执行,有效地改善资源利用率,显著地提高系统的吞吐量,但却使系统变得更加复杂。如果不能采取有效的措施(同步),对并发进程的运行进行管理,将对系统资源的无序争夺给系统造成混乱,使并发处理的结果存在着不确定性。
进程同步的基本概念
1. 两种形式的制约关系
1) 间接相互制约关系
2) 直接相互制约关系
2. 临界资源(Critical Resouce)
许多硬件资源如打印机、 磁带机等,都属于临界资源,诸进程间应采取互斥方式,实现对这种资源的共享。
问题描述:有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为使生产者进程与消费者进程能并发执行,在两者之间设置了一个具有 n 个缓冲区的缓冲池,生产者进程将它所生产的产品放入一个缓冲区中;消费者进程可从一个缓冲区中取走产品去消费。不允许消费者进程到一个空缓冲区去取产品,也不允许生产者进程向一个已装满产品且尚未被取走的缓冲区中投放产品。
共享变量:Var n, integer; type item=…;
var buffer: array[0,1,…,n-1] of item;
in, out: 0,1,…,n-1; counter: 0,1,…,n;
局部变量:nextp, nextc;
并发执行的生产者和消费者进程:
producer: repeat
produce an item in nextp;
while counter=n do no-op;
buffer[in]:=nextp; in:=in+1 mod n; counter:=counter+1;
until false;
consumer: repeat
while counter=0 do no-op;
nextc:=buffer[out]; out:=(out+1) mod n; counter:=counter-1;
consumer the item in nextc;
until false;
counter:=counter+1;
register1:=counter; (1)
register1:=register1+1; (2)
counter:=register1; (3)
counter:=counter-1;
register2:=counter; (a)
register2:=register2-1; (b)
counter:=register2; (c)
counter:=3 执行顺序 1->2->a->b->3->c 执行结果 counter值为2
counter:=3 执行顺序 a->b->1->2->c->3 执行结果 counter值为4
3. 临界区(critical section)
由前所述可知,多个进程必须互斥地访问计算机中的临界资源。人们把在每个进程中访问临界资源的那段代码称为临界区(critical section)。
repeat
entry section
critical section;
exit section
remainder section;
until false;
4. 同步机制应遵循的规则
用来实现互斥的同步机制必须遵循下述四条准则:
(1) 空闲让进。
(2) 忙则等待。
(3) 有限等待。
(4) 让权等待。
硬件同步机制
虽然可以利用软件方法解决诸进程互斥进入临界区的问题,但有一定难度,并且存在很大的局限性,因而现在已很少采用。相应地,目前许多计算机已提供了一些特殊的硬件指令,允许对一个字中的内容进行检测和修正,或者是对两个字的内容进行交换等。可利用这些特殊的指令来解决临界区问题。
1. 关中断
2. 利用Test-and-Set指令实现互斥
3. 利用Swap指令实现进程互斥
信号量机制
1. 整型信号量
最初由Dijkstra把整型信号量定义为一个用于表示资源数目的整型量S,它与一般整型量不同,除初始化外,仅能通过两个标准的原子操作(Atomic Operation) wait(S)和signal(S)来访问。
wait(S): while S<=0 do no-op;
S:=S-1;
signal(S):S:=S+1;
2. 记录型信号量
记录型信号量机制则是一种不存在“忙等”现象的进程同步机制。但在采取了“让权等待”的策略后,又会出现多个进程等待访问同一临界资源的情况。为此,在信号量机制中,除了需要一个用于代表资源数目的整型变量value外,还应增加一个进程链表指针L,用于链接上述的所有等待进程。
procedure wait(S) procedure signal(S)
var S:semaphore; var S: semaphore;
begin begin
S.value:=S.value-1; S.value:=S.value+1;
if S.value<0 then block(S.L); if S.value<=0 then wakeup(S.L);
end end
3. AND型信号量
将进程在整个运行过程中需要的所有资源,采取原子操作方式,一次性全部分配给,待进程使用完后再一起释放。
Swait(S1,S2,…,Sn)
if S1>=1 and … and Sn>=1 then for i:=1 to n do Si:=Si-1;
endfor else
进程进入第一个不满足的资源阻塞队列;
endif
Ssignal(S1,S2,…,Sn)
for i:=1 to n do Si:=Si+1; //循环释放资源Si//
检查资源Si等待队列中的所有进程,如果通过Swait测试,
插入到就绪队列;否则插入到某个等待队列;
endfor;
记录型信号量的问题:当一次需要 N 个某类临界资源时,便要进行 N 次 wait(S)操作,显然这是低效的。
Swait(S1,t1,d1,…,Sn,tn,dn)
if S1>=t1and … and Sn>=tn then for i:=1 to n do Si:=Si-di;
endfor else
进程进入第一个不满足Si>=ti 的资源阻塞队列;
endif
Ssignal(S1,d1,…,Sn,dn)
for i:=1 to n do Si:=Si+di; //循环释放资源Si//
检查资源Si等待队列中的所有进程,如果通过Swait测试,
插入到就绪队列;否则插入到某个等待队列;
endfor;
关于“信号量集”,我们一般讨论的是如下几种特殊情况:
(1) Swait(S,d,d)。
信号量集中只有一个信号量 S,允许它每次申请 d 个资源,当现有资源数少于 d 时,不予分配。
(2) Swait(S,1,1)。
此时的信号量集已蜕化为一般的记录型信号量(S>1 时)或互斥信号量(S=1 时)。
(3) Swait(S,1,0)。
这是一种很特殊且很有用的信号量操作。当 S≥1 时,允许多个进程进入某特定区;当 S 变为 0 后,将阻止任何进程进入特定区。换言之,它相当于一个可控开关。
信号量的应用
1. 利用信号量实现进程互斥方法
设置一互斥信号量mutex,并设其初始值为1,然后将各进程访问该资源的临界区CS置于wait(mutex)和signal(mutex)操作之间即可。
进程互斥代码描述:
Var mutex: semaphore:=1;
begin
parbegin
process 1: begin process 2: begin
repeat repeat
wait(mutex); wait(mutex);
critical section critical section
signal(mutex); signal(mutex);
remainder seetion remainder section
until false; until false;
end end
parend
2. 利用信号量实现前趋关系
还可利用信号量来描述程序或语句之间的前趋关系。设有两个并发执行的进程P1和P2。P1中有语句S1;P2中有语句S2。我们希望在S1执行后再执行S2。
前驱关系的实现:
1. 设置一个初值为 0 的公用信号量 S
2. 在进程P1中:用S1; signal(S);
3. 在进程P2中:用wait(S); S2;
前趋图的代码描述:
Var a,b,c,d,e,f,g:semaphore: =0,0,0,0,0,0,0;
begin
parbegin
begin S1; signal(a); signal(b); end;
begin wait(a); S2; signal(c); signal(d); end;
begin wait(b); S3; signal(e); end;
begin wait(c); S4; signal(f); end;
begin wait(d); S5; signal(g); end;
begin wait(e); wait(f); wait(g); S6; end;
parend
end