操作系统 2.3进程同步

2.3 进程的同步与互斥

2.3.1 进程的同步的基本概念

1.临界资源

定义:一次仅允许一个进程使用的资源称为临界资源

eg:就好像早上起来在宿舍和同学抢厕所时,把我和同学比做两个进程,两者就是同步的协作关系,而厕所就是临界资源

访问临界资源的过程可分为4个部分:

(1)进入区:检查是否可以进入临界区,如果可以进入,设置正在访问临界区的标志,又可以防止其他进程进入临界区

(2)临界区:进程中访问临界资源的那段代码

(3)退出区:将正在访问临界区的标志清除

(4)剩余区

2.同步(直接相互制约关系)

比如说AB进程,A产生数据,B计算结果,AB公用一个缓存区。缓存区为空时,B不能运行,等待A向缓存区传递数据后B才能运行,缓存区满时,A不能运行,等待B取走数据后,A才能运行。此时AB为直接制约关系。

3.互斥(间接相互制约关系)

比如AB进程都需要使用打印机,如果进程A需要打印时, 系统已将打印机分配给进程B,则进程A必须阻塞。一旦进程B将打印机释放,系统便将进程A唤醒,并将其由阻塞状态变为就绪状态

互斥是针对不同进程对同一个临界资源来说的,

而同步是系统为了达到某个目的,有多个进程,进程和进程之间有先后顺序的这种制约关系

同步机制需要遵守的4个原则:

1)空闲让进:当无进程处于临界区时,表明临界资源处于空闲状态,应允许一个请求进入临界区的进程立即进入自己的临界区,以有效利用临界资源。

2)忙则等待:当已有进程进入临界区时,表明临界资源正在被访问,因而其他试图进入临界区的进程都必须等待,以保证对临界资源的互斥访问。

3)有限等待:对要求访问的临界资源的进程,应保证在有限时间内能进入自己的临界区,以免陷入“死等”状态。

4)让权等待:当进程不能进入自己的临界区时,应立即释放处理机,以免陷入"忙等"状态

2.3.2 实现临界区互斥的几种方法

1. 软件实现法

算法1:单标志法

在这里插入图片描述

算法思想:起始turn=0,相当于厕所是0号专属,其他人不得进入,所以当系统切换到1进程时,1号进程不得进入,所以违背了“空闲让进”的原则

算法2:双标志先检查法

思想:该算法的基本思想是在每一个进程访问临界区资源之前,先查看一下临界资源是否正在被访问,若被访问,该进程需等待;否则,进程才进入自己的临界区。为此,设置了一个数据flag[i],如果第i个进程值为FLASE,则表示Pi进程未进临界区,值为TRUE,表示Pi进程进入临界区。
在这里插入图片描述

评价:如果访问顺序是15开头(例如15263748),则后面0号和1号可以同时进入临界区,违背了临界区忙则等待的原则,

就好像先进厕所的那个人就直接反锁厕所门,而违背忙则等待原则的意思就是厕所门还没上锁的时候,另一个人就冲进了厕所,两个人,一个坑,可能会面面相觑。。。

算法3:双标志后检查法

思想:先将flag[0]置为true(上锁),再进行检查,如果flag[1]为false(即p1进程没有上锁),则检查完毕,进入临界区,p1也是同理
在这里插入图片描述

先上锁,后检查,如果按照1526。。。的顺序来进行的话,就无法满足空闲让进和有限等待的原则,从而导致饥饿现象

虽然解决了算法2忙则等待的问题,但是,产生了更多更复杂的问题

算法4:peterson算法

思想:相当于a,b两人抢厕所,
a:我想用马桶
b:我想用马桶
a:你先用把
b:你先用吧
a:那好吧,我先用咯
最后是b说的客气话,所以a先用

在这里插入图片描述

算法评价:
优点:遵从了空闲让进,忙则等待,有限等待的原则
缺点:没有遵从让权等待的原则

2. 硬件实现方法

1)中断屏蔽方法

关中断,临界区,开中断。
优点:简单高效
缺点:只适用于内核进程(因为关中断只能在内核态运行)

2)TestAndSet指令

//布尔型共享变量lock表示当前临界区是否被加锁
//true表示已加锁,false表示未加锁
bool TestAndSet(bool *lock){
    
    
	bool old;
	old=*lock;     //用old来存放lock原来的值
	*lock=true;	   //无论之前有无加锁,现在设为加锁
	return old;    //返回lock原来的值
//下面是使用tsl指令实现互斥的算法逻辑
while(TestAndSet(&lock));
临界区代码。。。
lock=false;        //解锁
剩余区代码段。。。

2)Swap指令

Swap是用硬件实现的,执行过程中不允许被中断

Swap(bool *a,bool *b){
    
    
	bool temp;
	temp=*a;
	*a=*b;
	*b=temp;
//以下使用Swap实现互斥的算法逻辑
//lock表示当前临界区是否加锁
bool old=true;
while(old==true){
    
    
	Swap(&lock,&old);
临界代码区。。。
lock=false;    //解锁
剩余区域代码。。。

2.3.3 信号量

引入信号量的原因:双标志先检查法的检查和上锁不能一气呵成,先检查后上锁中,会导致在检查和上锁的间隙发生进程切换,从而使得多个进程占用临界资源的情况发生,所以,引入信号量就是为了使检查和上锁可以一气呵成。

信号量可以理解成一种类型的变量(类似于int,double等等)但是区别在于信号量不能进行加减乘除,只能进行初始化,P操作(wait)和V(signal)操作

1.整型信号量

void wait(int S){
    
    		//wait原语相当于“进入区”
	while(S<=0);        //如果资源数不够,就一直循环等待
	S=S-1;				//如果资源数够,就占用一个资源
}
void signal(int S){
    
    		//signal原语相当于是“退出区”
	S+=1;				//使用完资源后,在退出区释放资源
}
进程p0:
...
wait(S);				//进入区,申请资源
使用打印机资源...		//临界区,访问资源
signal(S);				//退出区,释放资源
...

2.记录型信号量(重点)

//定义记录型信号量
typedef struct{
    
    
	int value;
	struct process *L;
} semaphore;
//某些进程需要申请资源时,用wait原语申请
void wait(semaphore S){
    
    
	S.vslue--;
	if(S.value<0){
    
    
		block(S.L);//若资源数不够,使用block原语使进程从运行态变成阻塞态
				   //挂到阻塞队列L中
	}
}
void signal(semaphore S){
    
    
	S.vslue++;
	if(S.value <= 0){
    
    //S.value==0时,说明S.value本身为-1,所以说明原来有进程在等待资源分配
		wakeup(S.L);//若资源数不够,使用wakeup原语使进程从阻塞态变为运行态
	}
}
//尤其注意block和wakeup原语的使用

3.利用信号量实现进程同步

  1. 先确定同步关系,必须保证一前一后的两种操作
  2. 设置同步信号量S=0
  3. 前操作之后进行V(S)操作
  4. 后操作之前进行P(S)操作

其中,前操作为P1操作,后操作为P2操作

在这里插入图片描述

4.利用信号量实现进程互斥

  1. 先确定临界区
  2. 设置互斥信号量mutex=1
  3. 临界区之前进行P(mutex)操作
  4. 临界区之后进行V(mutex)操作

5.利用信号量实现进程的前驱关系

操作方法与3.利用信号量实现进程同步类似
在这里插入图片描述

2.3.4管程

猜你喜欢

转载自blog.csdn.net/be_stronger/article/details/113878733