进程同步
一、进程同步的基本概念
1.临界资源
一次仅允许一个进程使用的资源称为临界资源。
临界资源的访问:
do{
entry section;//进入区,设置正在访问标志,以阻止其他进程同时访问。critical section;//临界区,访问临界资源那段代码,临界段。exit section;//退出区,清除正在访问标志。remainder section;//代码中剩余的部分。}while(true)
2.同步同步亦称直接制约关系,是指为完成某种任务而建立的两个或多个进程,这些进程因为需要 在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。进程间的直接制约关系源于它们之间的相互合作。
3.互斥互斥也称间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待,当 占用临界资源的进程退出临界区后,另一进程才允许去访问此临界资源
同步机制应遵循的准则空闲让进
忙则等待
有限等待:对于请求访问的进程,应保证能在有限的时间内进入临界区。
让权等待:进程不能进入临界区时,应立即释放处理器,防止进程忙等待。
忙等状态:
当一个进程正处在某临界区内,任何试图进入其临界区的进程都必须进入代码连续循环,陷入忙等状态。连续测试一个变量直到某个值出现为止,称为忙等。
让权等待:
当进程不能进入自己的临界区时,应立即释放处理机,以免进程陷入“忙等”状态~(受惠的是其他进程)
二、实现临界区互斥的基本方法
1.软件实现方法
1)单标志法
用公共变量turn标志该谁进入临界区
//P0进程
while(trun != 0);
critical section;
turn=1;
remainder section;
//P1进程
while(turn != 1);
critical section;
turn=0;
remainder section;
这个方法的缺点是两个进程必须交替进去临界区,如果一个进程不进入的话,turn就无法再被改变了,就会导致另一个进程一直等待下去。
2)双标志法先检查:
设置了两个标志,分别表示两个进程的状态,可以不用交替执行,可以连续使用。
//Pi进程
while(flag[j]);//1
flag[i]=TRUE;//3
critical section;
flag[i]=FALSE;
remainder section;
//Pj进程
while(flag[i]);//2
flag[j]=TRUE;//4
critical section;
flag[i]=FALSE;
remainder section;
但是也有缺点,比如进程 i 执行语句1,先判断进程 j 没有要进入临界区。同时 j 也在执行语句2,它觉得 i 也没有去临界区的意思。于是 i 把自己的flag置为了TURE,此时 j 在判断时 i 还没来得及置标志,所以 j 也把自己置成了TRUE,然后此时有两个进程都在临界区,违背了基本原则。
3)双标志法后检查
//Pi进程
flag[i]=TRUE;
while(flag[j]);
critical section;
flag[i]=FALSE;
remainder section;
//Pj进程
flag[j]=TRUE;
while(flag[i]);
critical section;
flag[i]=FALSE;
remainder section;
这个类似先检查,如果 J I 都同时上来把自己置为了TRUE,则会相互无线等待下去,造饥饿现象。
4)peterson's Algotithm
//Pi进程
flag[i]=TRUE;
turn=j;
while(flag[i]&&turn==j)
critical section,
flag[i]=false;
remainder section;
//Pj进程
flag[i]=TRUE;
turn=j;
while(flag[i]&&turn==j);
critical section;
flag[i]=false;
remainder section;
一个标志自己的状态(申明),一个标志该谁使用临界区(钥匙)。
也就是只有一把钥匙。就是双方想执行的时候都要把钥匙先给对方,所以无论是谁先申明还是同时申明,最后钥匙是在一个人手里的,然后在进入临界区之前判断,如果对方想要进入而且拥有钥匙,我就等待。如果对方没有钥匙或者不想进入,那我就进入。不可能出现两个人都有想进入两个人都有钥匙的情况(钥匙只有一把),解决了死等的问题。
如果此时 j 执行完了,不再执行了。while 就会放开 i ,i 可以继续执行。即使后 j 都不再执行力,i 也不会再次被 while 捕捉。解决了交替执行的问题。
2.硬件实现的方法
1)中断屏蔽法
进入临界区后禁止一切中断发生。这种方法限制了处理机交替执行程序的能力,因此执行的效率会明显降低。对内核来说,在 它执行更新变量或列表的几条指令期间,关中断是很方便的,但将关中断的权力交给用户则很不明智,若一个进程关中断后不再开中断,则系统可能会因此终止。
2)硬件指令的方法
TestAndSet指令:原子操作,执行过程不允许被中断,其功能是读出某标志后置为真。
boolean TestAndSet(boolean *lock){
boolean old;
old=*lock;
*lock=true;
return old;
}
进入临界区之前使用TestAndSet检查修改标志lock,如果为true则重复检查。
如果为false则不会被while捕捉,如果为true就相当于一直在置true,直到其他进程将lock置为了false,while才放走。
while TestAndSet(&lock);
进程的临界代码块;
lock=false;
进程的其他代码;
swap指令:交换两个字节的内容
Swap(boolean *a,boolean *b){
boolean temp;
temp=*a;
*a=*b;
*b=temp
}
这里key是局部变量,lock是共享变量,同样可以理解为声明和钥匙。
申明我要进入临界区,然后进入while循环,此时如果临界区没人,lock等于false,此时钥匙在,然后swap交换之后key拿到了false值(钥匙),就可以走出循环进入临界区。
key=true;
while(key!=false)
swap(&lock,&key);
进程临界区代码段;
lock=false;
进程其他代码;硬件方法的优点:适用于任意数目的进程,而不管是单处理机还是多处理机。简单、容易验 证其正确性。
硬件方法的缺点 :进程等待进入临界区要耗费处理机时间,不能实现让权等待。从等待进程中随机选择一个进入临界区,有的进程可能一直选不上,从而导致“饥饿”现象。
三、信号量
1.整型信号量
wait(S)/signal(S)
可记为P操作和V操作
wait(s){
while(s<=0);
s=s-1
}
singal(s){
s=s+1
}
在wait中,会一直去测试s是否小于等于0,所以该机制还是处于一个忙等的状态。
2.记录型信号量
相比整型,他不存在忙等现象。
typedef struct{
int value;
struct process *L;
} semaphone
void wait(semaphore s){
s.value- -;//申请一个资源,老板我要一个小熊饼干
if(s.value<0){//如果该类资源不够,老板说小熊饼干卖完了
add this process to s.L;//加入等待队列,老板说帮你预定一个你明天来拿
block(s.L);//block原语,自我阻塞,释放处理器资源,回家去等
}
}
void signal(semaphonre s){
s.value++;//释放一个资源,有个顾客退回了一个小熊饼干
if(s.value<==0){//老板发现还有人在等
remove a process p from s.L;//打电话叫他来拿了
wakeup(p);//唤醒进程,来店里了
}
}
3.利用信号量实现同步
semahore s=0;//初始化信号量
p1(){
....
x;//语句x
V(s);//告诉p2语句x已经完成
...
}
p2(){
...
p(s);//看看x是不是已经完成
y;//无误,执行y
..
}
4.利用信号量实现互斥
semaphore s=1;//初始化信号量
p1(){
..p(s);//上锁
进程P1临界区
v(s);//开锁
..
}
p2(){
..
p(s);//上锁
进程p2的临界区;
v(s);//开锁
..
}
5.实现前驱关系为了保证执行顺序,设置信号量如图
semaphore a1=a2=b1=b2=c=d=e=0;
s1(){
..;
v(a1);
v(a2);
}
s2(){
p(a1);
...;
v(b1);
v(b2);
}
s3(){
p(a2);
..;
v(c);
}
s4(){
p(b1);
..;
v(d);
}
s5(){
p(b2);
..;
v(e);
}
s6(){
p(c);
p(d);
p(e);
..
}
6.分析进程同步和互斥问题的方法步骤
1)分析关系
2)整理思路
3)设置信号量
四、管程
1.定义代表共享资源的数据结构 ,以及由对该共享数据结构实施操作的一组过程所组成的资源管理程序,称为管程( monitor )。
管程由四个部分组成:
1)名称
2)局部于管程内部的共享数据结构数据说明
3)对该数据结构进行操作的一组过程
4)对局部于管程内部的共享数据设置初始值的语句
例如
monitor Demo{
//定义共享数据结构
共享数据结构s;
//初始化数据结构
init_code(){
s=5;//初始资源
}
take_away(){
对共享数据结构x的一系列处理;
s--;
..
}
give_back(){
对共享数据结构x的一些系列处理;
s++;
..
}
}
2.条件变量
当一个进程进入管程后被阻塞,如果一直阻塞的原因没有解除,则不会释放管程则其他进程不能进入管程。
monitor Demo{
共享数据结构;
condition x;
init_code(){
..
}
take_away(){
if(s<=0) x.wait();//资源不够,在x上阻塞等待
资源足够,分配资源,处理;
}
give_back(){
归还资源,做处理;
if(有进程在等待)x.signal;//唤醒一个阻塞进程
}
}