第3章 进程的同步与通信.docx
《第3章 进程的同步与通信.docx》由会员分享,可在线阅读,更多相关《第3章 进程的同步与通信.docx(53页珍藏版)》请在冰豆网上搜索。
第3章进程的同步与通信
第3章进程的同步与通信
3.2例题解析
例3.2.1多道程序系统程序的执行失去了封闭性和再现性,因此多道程序的执行不需要这些特性,这种说法是否正确?
解这种说法不正确。
可以想象,如果一个程序在多道程序系统中,在相同的输入的情况下,多次执行所得结果是不同的,有谁还敢使用这个程序?
因此,多道程序的执行也需要封闭性和再现性,只不过单道程序系统的封闭性和再现性是先天固有的,多道程序系统的程序执行要想获得封闭性和再现性,需通过程序员的精心设计才能得到。
所使用的方法就是同步和互斥的方法。
例3.2.2多个进程对信号量S进行了5次P操作,2次V操作后,现在信号量的值是-3,与信号量S相关的处于阻塞状态的进程有几个?
信号量的初值是多少?
解
(1)因为S的当前值是-3,因此因为S处于阻塞状态的进程有3个;
(2)因为每进行一次P(S)操作,S的值都减1,每执行1次V操作S的值加1,故信号量的初值为-3+5-2=0;
例3.2.3如下锁的实现方法存在什么缺点?
如何改进?
LOCK(X)UNLOCK(X)
{{
dowhileX=1;X=0;
X=1
}}
解存在的缺点是:
当锁是关闭时,采用的是循环等待的方法,这样的等待还是要占用处理机的时间,应该采用阻塞等待的方法。
改进的锁实现如下:
LOCK(X)UNLOCK(X)
{{
ifX.value=1ifnotempty(X.L)
{insert(*,X.L);{P=remove(X.L);
Block(*)Wakeup(P)
}}
elseX.Value=1elseX.Value=0
}}
这里X.value是锁的值,X.L是存放由于锁X而阻塞的进程的队列。
insert(*,X.L)将当前进程的进程号插入到X.L,remove(X.L)是从X.L中移出一个进程号。
例3.2.4使用多个进程计算Y=F1(X)+F2(X).
解
(1)确定并发和顺序操作
在这个问题中,F1(X)和F2(X)的计算是可以并行处理的,因此F1(X)和F2(X)可以分别出现在两个进程中。
(2)确定互斥或同步的规则
在F1(X)+F2(X)中,必须在F1(X)和F2(X)计算完毕,才能进行加法运算,因此本问题是同步问题。
(3)同步的操作流程
〈进程main〉
创立进程p1来计算F1(X);
创立进程p2来计算F2(X);
F1(X)计算是否完成?
没有,等待;
F2(X)计算是否完成?
没有,等待;②
进行加法运算。
〈进程p1〉
y1=F1(X);
设置F1(X)计算完成标志;③
〈进程p2〉
y1=F2(X);
设置F2(X)计算完成标志。
④
(4)确定信号量的个数和含义
根据同步规则以及操作流程确定信号量的个数是2个,S1和S2:
S1含义是F1(X)计算是否完成;
S2含义是F2(X)计算是否完成。
(5)确定信号量的初值
S1=0;
S2=0。
(6)确定P、V操作的位置
上面①处是一个P操作,P(S1);
上面②处是一个P操作,P(S2);
上面③处是一个V操作,V(S1);
上面④处是一个V操作,V(S2)。
解法1
Main()
Publicy,y1,y2,.P1,P2
SemaphoreS1,S2
{
S1=0;
S2=0;
P1=Creat(N-F1,F1,x,……);
P2=Creat(N-F2,F2,x,……);
P(S1);
P(S2);
y=y1+y2;
}
ProcedureF1(x)
{
y1=计算1;
V(S1);
}
ProcedureF2(x)
{
y2=计算2;
V(S2)
}
解法2
Main()
Publicy,y1,y2,.P1,x
SemaphoreS1
{input(x);
S1=0;
P1=Creat(N-F1,F1,x,……);
Y2=F2(x);
P(S1);
y=y1+y2;
}
ProcedureF1(x)
{
y1=计算1;
V(S1)
}
采用2个进程和1个信号量来实现Y=F1(X)+F2(X)的时候,采用的方法是父进程创立子进程,F1(X)在子进程中计算,F2(X)在父进程中计算,因此F1(X)和F2(X)计算仍然是并发进行的。
S1信号量的含义为F1(X)是否完成。
改进的方法比原来的方法节约一个进程和一个信号量,但并发操作的程度并没有降低。
例3.2.5生产者—消费者问题演变。
情况1一个buffer,一个生产者,一个消费者,生产者只生产一个东西,消费者只进行一次消费,即:
生产者只进行一次putdata操作,消费者只进行一次getdata操作。
解这是一个同步问题,生产者和消费者分别是2个并发的进程。
(1)操作规则
如果buffer为空,则消费者只能等待。
(2)操作流程
<生产者>
{
putdata;
设置Buffer有数据标志V(S)
}
<消费者>
{
判断buffer是否有产品,没有则等待;
getdata;
}
(3)信号量
设置1个信号量full,full表示buffer是否有数据,初值为0。
(4)P、V操作实现
varfull:
semaphore:
=0;
buffer:
array[1]ofitem;
begin
parbegin
producer:
begin
putdata;
V(full);
end
consumer:
begin
P(full);
getdata;
end
parend
end
情况2一个buffer,一个生产者,一个消费者,生产者不断地进行putdata操作,消费者不断地进行getdata操作,即:
生产者不断地生产,消费者不断地消费
(1)操作规则
只有buffer为空时才能进行putdata操作;只有buffer有数据时才能进行putdata操作。
(2)操作流程
<生产者>
{
repeat
判断buffer是否为空,不空则等待;
putdata;
设置buffer有数据的标志;
untilfalse
}
<消费者>
{
repeat
判断buffer是否有数据,没有数据则等待;
getdata;
设置buffer为空标志;
untilfalse
}
(3)信号量
设置2个信号量full和empty。
full表示buffer是否有数据。
因为进程在初始状态时,buffer中没有数据,故初值为0,变化范围-1~1。
empty表示buffer是否为空。
因为进程在初始状态时,buffer为空,故初值为0初值为1,变化范围-1~1。
(4)P、V操作实现
varfull:
semaphore:
=0;
emptyl:
semaphore:
=1;
buffer:
array[1]ofitem;
begin
parbegin
producer:
begin
repeat
P(empty);
putdata;
V(full);
untilfalse
end
consumer:
begin
repeatP(full);
getdata;
V(empty);
untilfalse.
end
parend
end
情况3一个buffer,多个生产者,多个消费者,多个生产者和消费者都在不断地存取buffer,即生产者不断地进行putdata操作,消费者不断地进行getdata操作。
(1)操作规则
只有buffer为空时才能进行putdata操作;只有buffer有数据时才能进行putdata操作。
这时buffer变成了临界资源,不允许多个进程同时操作buffer,即不允许多个消费者同时进行gedata,不允许多个生产者同时进行putdata操作。
(2)操作流程
<生产者>
{
repeat
判断buffer是否为空,不则等待;
是否可操作buffer;
putdata;
设置buffer可操作标志;
设置buffer有数据的标志;
untilfalse
}
<消费者>
{
repeat
判断buffer是否有数据,没有则等待;
是否可操作buffer;
getdata;
设置buffer可操作标志;
设置buffer为空标志;
untilfalse
}
(3)信号量
设置3个信号量full、empty和B-M。
full表示buffer是否有数据,初值为0;
empty表示buffer是否为空,初值为1;
B-M表示buffer是否可操作,初值为1。
由于buffer只有一个,full和empty可以保证对buffer的正确操作,故B-M是多余的,可以省略。
(4)P、V操作实现
<生产者><消费者>
{{
repeatrepeat
P(empty);P(full);
P(B-M); P(B-M);
putdata;getdata;
V(B-M); V(B-M);
V(full);V(empty);
untilfalse.untilfalse
}}
情况4多个生产者,多个消费者,N个buffer,多次循环存取buffer,即,即多个生产者不断地进行putdata操作,多个消费者不断地进行getdata操作。
(1)操作规则
只有buffer有空间才能进行putdata操作;
只有buffer有数据才能进行putdata操作;
这时buffer变成了临界资源,不允许多个消费者和生产者同时对同一个buffer进行gedata和putdata操作。
(2)操作流程
<生产者>
{
repeat
判断buffer是否有空间,没有则等待;
是否可操作buffer;
putdata;
设置buffer可操作标志;
设置buffer有数据的标志;
untilfalse
}
<消费者>
{
repeat
判断buffer是否有数据,没有则等待;
是否可操作buffer;
getdata;
设置buffer可操作标志;
设置buffer有空间标志;
untilfalse
}
(3)信号量
full表示buffer是否有数据,初值为0;
empty表示buffer是否为空,初值为N;
B-M表示buffer是否可操作,初值为1。
(4)P、V操作实现
<生产者><消费者>
{{
repeatrepeat
P(empty);P(full);
P(B-M); P(B-M);
putdata;getdata;
V(B-M); V(B-M);
V(full);V(empty);
untilfalseuntilfalse
}}
(5)改进的P、V操作实现
在上述的实现中,putdata和getdata操作都在临界区中,因此多个进程对多个buffer的操作是不能并发进行的,进程间并行操作的程度很低。
实际上只要保证多个进程同时操作不同buffer就可以实现对整个buffer的并行操作。
因此,只要保证为不同的进程分配不同buffer,putdata和getdata操作是可以同时进行。
这样互斥不是发生在对buffer的存取操作上,而是发生在对buffer的分配上,这个时间与存取buffer的时间相比是较短的,因此减少了进程处于临界区的时间。
这里引入2个函数:
getE_buffer(),返回值是空的buffer号;
getD_buffer(),返回值是有数据的buffer号。
getE_buffer()和getD_buffer()通过将buffer转换成循环队列的方法来实现对buffer的分配。
buffer设有Pbuff,Pdata两个指针,分别指向空闲buffer和有数据buffer 的头,每进行一次getE_buffer()和getD_buffer(),Pbuff和Pdata两个指针分别向后移动一个位置。
GetE_buffer()
{c=Pbuff
Pbuff=(Pbuff+1)MODN;
Return(c)
}
getD_data()
{c=Pdata;
Pdata=(pdata+1)MODN;
Return(c)
}
改进的程序描述如下:
varmutex.empty,full:
semaphore:
=1,n,0;
buffer:
array[0,…,n-1]ofitem;
Pbuff,Pdata:
integer:
=0,0;
begin
parbegin
producer:
begin
repeat
┇P(empty);
P(B_M);
in:
=getE_buffer();
V(B_M)
putdata(in);V(full);
untilfalse;
end
consumer:
begin
repeat
P(full);
P(B_M);out:
=getD_buffer();
V(B_M);
Getdata(out);
V(empty);
untilfalse
end
parend
end
例3.2.6设公共汽车上,司机和售票员的活动分别为:
司机的活动为启动车辆,正常行车,到站停车;售票员的活动为关车门,售票,开车门。
试问:
(1)在汽车不断地到站、停车、行驶过程中,司机和售票员的活动是同步关系还是互斥关系?
(2)用信号量和P、V操作实现他们间的协调操作。
解
(1)确定并发和顺序操作
在这个问题中,司机与售票员间是并行操作的,司机和售票员本身的操作是顺序进行的。
因此司机是一个进程,售票员是一个进程,它们之间是同步关系。
(2)确定同步的规则
根据一般常识,司机和售票员在车上的操作规则如下:
1)售票员操作的规则是只有司机停车后,售票员才能开门让乘客上下车;
2)司机操作的规则是只有售票员关门后,司机才能启动开始行驶汽车。
可见,售票员关车门后,要向司机发开车信号,司机接到开车信号后才能启动车辆。
在汽车正常行驶过程中售票员售票,到站时司机停车,售票员在车停后开车门,让乘客上下车。
因此司机启动车辆的动作必须与售票员的动作取得同步;售票员开车门的动作也必须同司机停车取得同步。
(3)进程的操作流程
<售票员进程>
dowhileT
关车门;
设立车门已关标志;
售票;
是否停车?
没停则等待;
开车门;
上下乘客;
enddo
<司机进程>
dowhileT
是否关门?
没关则等待;
启动车辆;
正常行车;
到站停车;
设立车停标志;
enddo
(4)确定信号量的个数和含义
根据同步规则以及操作流程确定信号量的个数是2个,S1和S2。
S1的含义是否关门;
S2的含义是否停车。
(5)确定信号量的初值
S1=0;
S2=1。
(6)确定P、V操作的位置
司机操作中,是否关门?
没关则等待,这是一个P操作,P(S1);
司机操作中,设立停车标志,这是一个V操作,V(S2);
售票员操作中,是否停车?
没停则等待,这是一个P操作,P(S2);
售票员操作中,设立关门标志,这是一个V操作,V(S1)。
(7)P、V操作实现
intS1=0;
intS2=1;
main()
{
cobegin
driver();busman();
coend
}
driver()
{dowhileT
{P(S1);
启动车辆;
正常行车;
到站停车;
V(S2);
}
}
busserver()
{dowhileT
{关车门;
V(S1);
售票;
P(S2);
开车门;
上下乘客;
}
}
例3.2.7在OS中引入管程的目的是什么?
解在OS中引入管程的目的是为了更简便、更可靠地解决进程之间的同步、互斥问题。
在未引入管程之间,进程间的同步、互斥问题是由程序员处理的。
例如,在临界区的前后插入P、V操作。
但是,由程序员处理同步、互斥问题有可能引入种种人为的错误。
管程主要是管理对共享数据的操作和使用,即把对共享数据互斥使用的控制这一任务从程序员身上,交由编译程序去处理,这样既方便了编程,又不会产生人为的同步、互斥上的错误。
例3.2.8说明管程中条件变量的含义及作用。
解管程中的条件变量(类型为Condition)是供调用管程中外部过程的进程执行Wait和Signal操作将自己挂起和解挂的变量。
可通过与信号量的比较来认识条件变量:
(1)信号量要赋初值而条件变量不赋初值,它只是一个队首指针;
(2)在信号量上执行P操作的进程不一定会被阻塞,而在条件变量上执行Wait操作的进程肯定被挂起;
(3)在信号量上的V操作将导致信号量值加1,若增加后的值小于等于0,则要唤醒在信号量上等待的进程,但唤醒者不受什么影响;而在条件变量上的Signal操作却有两种处理方式:
P等待,直到Q离开管程,或等待另一条件;Q等待,直到P离开管程,或等待另一条件。
P、Q是两个进程。
例3.2.9如果信号量S的初值是5,现在信号量的值是-5,那么系统中的相关进程至少执行了几个P(S)操作?
与信号量S相关的处于阻塞状态的进程有几个?
如果要使信号量S的值大于0,应该进行怎样的操作?
解
(1)因为每执行一次P操作S的值减1,5-(-5)=10,在这期间有可能有进程执行V操作,使S的值加1,所以至少执行了10次P(S);
(2)5个;
(3)6个V(S)或5个以上。
例3.2.10如下图所示,有多个PUT操作同时向BUFF1放数据,有一个MOVE操作不断地将BUFF1的数据移到Buff2,有多个GET操作不断地从Buff2中将数据取走。
BUFF1的容量为m,BUFF2的容量是n,PUT、MOVE、GET每次操作一个数据,在操作的过程中要保证数据不丢失。
试用P、V原语协调PUT、MOVE的操作,并说明每个信号量的含义和初值。
图4.2进程操作图
解
(1)确定并发的操作
本问题是把2个消费者和生产者问题综合在一起。
多个PUT操作与一个MOVE操作并发进行,多个GET操作与一个MOVE操作并发进行。
因此本题涉及三类进程:
PUT类进程,有多个;GET类进程,有多个;MOVE类进程,有1个。
(2)操作规则
1)只有buff1有空间才能进行PUT操作;
2)只有buff1有数据,buff2有空间才能进行MOVE操作;
3)只有buff2有数据才能进行GET操作;
4)不允许多个进程同时操作buff1;
5)不允许多个进程同时操作buff2。
(3)操作流程
{
repeat
判断buff1是否有空间,没有则等待;
是否可操作buff1;
PUT;
设置buff1可操作标志;
设置buff1有数据的标志;
untilfalse
}
{
repeat
判断buff1是否有数据,没有则等待;
判断buff2是否有空间,没有则等待;
是否可操作buff1;
是否可操作buff2;
MOVE;
设置buff1可操作标志;
设置buff2可操作标志;
设置buff1有空间标志;
设置buff2有数据标志;
untilfalse
}
{
repeat
判断buff2是否有数据,没有则等待;
是否可操作buff2;
GET;
设置buff1可操作标志;
设置buff1有空间标志;
untilfalse
}
(4)信号量
设置6个信号量full1、empty1、B-M1、full2、empty2、B-M2,它们的含义和初值如下:
1)full1表示buff1是否有数据,初值为0;
2)empty1表示buff1有空间,初值为m;
3)B-M1表示buff1是否可操作,初值为1;
4)Full2表示buff2是否有数据,初值为0;
5)Empty2表示buff2有空间,初值为n;
6)B-M2表示buff2是否可操作,初值为1;
(5)P、V操作实现
{
repeat
P(empty1);/*判断buff1是否有空间,没有则等待*/
P(B-M1);/*是否可操作buff1*/
PUT;
V(B-M1);/*设置buff1可操作标志*/
V(full1);/*设置buff1有数据的标志*/
untilfalse
}
{
repeat
P(full1);/*判断buff1是否有数据,没有则等待*/
P(empty2);/*判断buff2是否有空间,没有则等待*/
P(B-M1);/*是否可操作buff1*/
P(B-M2);/*是否可操作buff2*/
MOVE;
V(B-M1);/*设置buff1可操作标志*/
V(B-M2);/*设置buff2可操作标志*/
V(empty1);/*设置buff1有空间标志*/
V(full2);/*设置buff2有数据标志*/
untilfalse
}
{
repeat
P(empty2);/*判断buff2是否有空间,没有则等待*/
P(B-M2);/*是否可操作buff2*/
GET;
V(B-M2);/*设置buff2可操作标志*/
V(full2);/*设置buff2有数据的标志*/
untilfalse
}
例3.2.11一售票厅只能容纳300人,当少于300人时,可以进入;否则,需在外等候。
若将每一个购票者作为一个进程,请用P、V操作编程,并写出信号量的初值。
解〈购票者进程〉
{┋
P(S);
进入售票厅;
购票;
退出售票厅;
V(S);
}
信号量的初值
S=300
例3.2.12设A、B为两个并发进程,它们共享一个临界资源,其执行临界区的算法框图如下图所示。
试判断该算法是否有错?
请说明理由。
如果有错,请改正。
S1、S1的初值为0,CSA、CSB为临界区。
A进程B进程
图4.3进程操作图
分析咋一看,好像是A进程开始未执行P操作,便直接进入临界区,以为问题出在这里。
从图中可分析出A、B两进程的执行思路:
A进程对临界资源操作结束后,将其释放给B进程,而B进程对临界资源操作结束后,将释放给进程A。
系统初启时,S1、S2初值为0,则B执行P(S1)被阻塞,而A进程可直