进程同步与互斥

本文深入探讨了进程同步与互斥的概念,重点讲解了信号量机制,包括整型和记录型信号量,以及PV操作。通过实例解释了临界资源、临界区和两种资源共享方式。同时,介绍了实现进程同步和互斥的各种方法,如单标志法、双标志法、Peterson算法以及中断屏蔽和硬件指令。这些机制确保了并发进程的有序性和资源的正确访问。
摘要由CSDN通过智能技术生成

1.进程同步与互斥

实现进程同步与互斥的工具主要有两种:
1.锁(加锁、解锁)
2.信号量:PV操作

1.0 信号量

信号量代表系统中资源的数量

1.0.1 整型信号量

整型信号量:用一个整型数代表系统资源数量
缺点:进程会出现“忙等”(即一个进程占用临界资源,另一个就得一直等待)

int S=1;	//初始化信号量,代表当前系统资源数量为1,如打印机
//P操作(荷兰语 Passeren--“通过”)
void wait(int S){	//检查和上锁一气呵成
	while(S<=0);	//若资源数量不足,进程在此阻塞等待,这里会出现“忙等”
	S=S-1;	//资源数量够用,则进程占用一个资源,系统资源数量减1
}
//V操作(荷兰语 Vrijgeven--“释放”)
void signal(int S){
	S=S+1;	//进程释放一个资源后,资源数量加1
}

假设场景:当前S=1,进程P0正在访问某个资源【执行wait】,则S=0,若在此时发生了进程切换,进程P1也想访问该资源,执行wait函数时在while处阻塞等待,当进程P0退出临界区并释放资源后,进程P1才可进入临界区

//进程P0
...
wait(S);	//进入区,申请资源(P操作)
critical section	//临界区,访问资源
signal(S);	//退出区,释放资源(V操作)
remainder section	//剩余区

//进程P1
...
wait(S);	//进入区,申请资源(P操作)
critical section	//临界区,访问资源
signal(S);	//退出区,释放资源(V操作)
remainder section	//剩余区

1.0.2 记录型信号量

记录型信号量:用一个数据结构代表系统资源数量
解决整型信号量出现“忙等”的问题,让等待的进程进入阻塞队列

//记录型信号量的定义
typedef struct{
	int value;	//剩余资源数
	struct process *L;	//等待队列
} semaphore;
//一次P操作:申请一个单位的资源
//某进程需要使用资源时,通过wait申请
void wait(semaphmore S){
	S.value--;			//资源数减一
	if(S.value<0){		//资源数不足
	//信号量S的等待队列L,比如等待打印机的进程构成的队列
		block(S.L);		//发现资源不足,进程自己加入阻塞队列,主动放弃处理机,使其从运行态变为阻塞态,遵循让权等待,不会出现忙等
	}
}
//一次V操作:释放一个单位的资源
//某进程不需要使用资源时,通过signal释放
void signal(semaphmore S){	
	S.value++;			//资源数加一
	if(S.value<=0){		//资源数不足
	//信号量S的等待队列L,比如等待打印机的进程构成的队列
		wakeup(S.L);	//唤醒阻塞队列中的某进程,使其从阻塞态变为就绪态
	}
}

例子:如果现在有2台打印机(资源数value=2),4个进程均需要打印机A,进程0通过wait申请使用打印机,value=1,进程1通过wait申请使用打印机B,value=0,此时资源不足,那么进程3(value=-1)和进程4(value=-2)进入阻塞队列进行等待,进程0使用完释放打印机A(value=-2+1=-1),进程1使用完释放打印机B(value=-1+1=0)

1.1 临界资源、临界区

临界资源:一次仅允许一个进程使用的资源
临界区:访问临界资源的那段代码

//进入区和退出区是实现互斥的代码段
do{
	entry section //进入区:检查进程能否进入临界区,若能,则“上锁”(此时其他进程无法进入该临界区)
	crtitical section //临界区:访问临界资源的代码
	exit section	//退出区:进程完成访问,“解锁”(此时其他进程可以进入上面的临界区)
	remainder section	//剩余区:代码中的其余部分
}while(true)

1.2 两种资源共享的方式

1.2.1 同时共享

宏观上“同时”访问某资源,微观上交替访问某资源

例如:某两个进程共享一个物理内存

下图来自《小林coding》

并发与共享的关系
并发与共享共生共存
两个进程交替执行(并发)“同时”访问硬盘资源(两个进程共享了同一份资源)

1.2.2 互斥共享

例如:一个摄像头如果一个APP正在使用,则另一个APP无法使用

1.3 进程同步

同步关系:两个进程存在先后关系,例如A程序的输出是B程序的输入,意味着程序A必须在程序B前执行

1.3.1 为什么需要进程同步?

为提高效率,我们引入了并发性,而并发性带来了异步性,而异步性是指进程以不可预知的速度向前推进,进程何时结束,何时暂停不可预知。这时我们需要一种机制,使得多个进程能够合理有序地共同完成一个任务

1.3.2 实现进程同步的方法

1.3.2.1 信号量机制实现进程同步(PV操作)

P操作(申请资源)

S=S-1;	//尝试申请资源(申请并不一定成功)S代表当前可用资源个数
if(S < 0) //表示当前无可用资源,比如 0-1<0,第一个0表示无可用资源
//因在尝试申请时已无可用资源,所以需要将提出申请的该进程阻塞,将此进程挂到该资源的等待队列,调度另一个进程运行
if(S >= 0) //表示当前有可用资源,比如 1-1=0,第一个1表示当前可用资源为1
//因在尝试申请时还有可用资源,提出申请的该进程继续运行

V操作(释放资源)

S=S+1; //尝试归还/释放资源
if(S <= 0) //表示当前有等待该资源的进程,比如 -1+1=0,-1表示当前等待资源的进程为1
//唤醒等待该资源的第一个其他进程,该进程继续运行(释放完资源后继续做自己的事)
if(S > 0) //表示当前没有等待该资源的进程,无需唤醒任何其他进程,该进程继续运行

进程同步:并发进程有序推进,解决异步中无序推进带来的不确定性

//记录型信号量的定义
typedef struct{
	int value;	//剩余资源数
	struct process *L;	//等待队列
} semaphore;

同步信号量初始化为0,先V后P,则可以实现同步
刚开始该资源为0,只有某个进程释放该资源后,另一个进程才可使用该资源,这就保证了进程执行的先后次序

//信号量机制实现进程同步
semaphore S = 0;

P1(){
	...
	...
	V(S);	//释放资源
	...
}

P2(){
	P(S);	//获得资源
	...
	...
	...
}

进程同步的应用:信号量机制实现前驱关系

下图来自王道考研操作系统

1.4 进程互斥

互斥关系:某资源如摄像头同一时间段仅允许一个进程使用,不允许同一时间段内有两个以上进程使用,存在一种排斥关系,一个进程在访问某个资源时,其他进程如果此时也要访问该资源时,则必须等待

实现进程互斥需要遵循的原则:

  1. 空闲让进:临界区空闲的话,允许进程进入临界区
  2. 忙则等待:临界区繁忙的话,临界区外的进程需等待
  3. 有限等待:保证进程在有限时间内可以进入临界区,保证进程不会出现饥饿(等过长时间)
  4. 让权等待:当某个进程无法进入临界区时,立即释放处理器,防止进程忙等待

1.4.1 为什么需要进程互斥?

例如:打印两个进程A、B的数据,当进程A正在数据的过程中,进程B也来打印,则打印出来的内容就会是进程A与B的掺杂,所以进程互斥机制的有必要的

1.4.2 实现进程互斥的方法

1.4.2.1 实现进程互斥的软件方法
1.4.2.1.1 单标志法

在进入区只做“检查”,不上锁

int turn=0;//表示当前允许进入临界区的进程号
//进程P0
while(turn != 0);	//进入区,检查
critical section;	//临界区
turn = 1;			//退出区
remainder section;	//剩余区

//进程P1
while(turn != 1);	//进入区
critical section;	//临界区
turn = 0;			//退出区
remainder section;	//剩余区


实现了同一时刻最多有一个进程进入临界区

缺点:如果刚开始进程P0没有进入临界区,也就意味着随后的退出区内代码也不会执行,这样就导致标志位永远不会被修改,标志位永远为0,则进程P1永远无法进入临界区,临界区一直处于空闲状态.违背了“空闲让进”

1.4.2.1.2 双标志法先检查

在进入区先“检查”,后上锁

bool flag[2];
flag[0]=false;	//表示进程0不想进入临界区
flag[1]=false;	//表示进程1不想进入临界区

//进程P0
while(flag[1]);	//询问进程1是否想要进入临界区	    //进入区(检查)
flag[0]=true;	//进程0想要进入临界区			//进入区(上锁)
critical section;	//临界区
flag[0]=false;	//退出区
remainder section;	//剩余区

//进程P1
while(flag[0]);	//询问进程0是否想要进入临界区	    //进入区(检查)
flag[1]=true;	//进程1想要进入临界区			//进入区(上锁)
critical section;	//临界区
flag[1]=false;	//退出区
remainder section;	//剩余区


缺点:如果进程P0与进程P1并发执行,就可能导致两个进程同时进入临界区,违背了“忙则等待”
原因:进入区的两句之间,也就是检查与上锁之间可能发生进程切换,这两句并没有一同处理

1.4.2.1.3 双标志法后检查

双标志法前检查中是先检查后上锁
双标志法后检查是对前者的改进,改为了先上锁后检查

bool flag[2];
flag[0]=false;	//表示进程0不想进入临界区
flag[1]=false;	//表示进程1不想进入临界区

//进程P0
flag[0]=true;	//进程0想要进入临界区			//进入区(上锁)
while(flag[1]);	//询问进程1是否想要进入临界区	    //进入区(检查)
critical section;	//临界区
flag[0]=false;	//退出区
remainder section;	//剩余区

//进程P1
flag[1]=true;	//进程1想要进入临界区			//进入区(上锁)
while(flag[0]);	//询问进程0是否想要进入临界区	    //进入区(检查)
critical section;	//临界区
flag[1]=false;	//退出区
remainder section;	//剩余区


缺点:如果出现中断,则执行顺序会改变,若改变如上图,执行完左侧第一句来了中断,然后执行右侧第一句、第二句、第三句,紧接着又来中断,随后执行左侧第二句、第三句,则会导致两个进程均无法进入临界区,都互相等对方进入。违背了“有限等待”和“空闲让进”

1.4.2.1.4 Peterson算法

单标志算法 + 双标志后检查算法 + turn = Peterson算法
利用 flag 解决了进程互斥访问临界资源
利用 turn 解决了“饥饿”现象(turn有谦让意味,某进程在进入临界区前询问其他进程,你想进入临界区吗?如果想,就先让其他进程执行,这样就能避免长时间等待)
如果进程卡在while语句,则该进程仍在CPU上一直循环判断,无法进入临界区,一直占用着CPU,此时应该释放处理器,让其他进程进入临界区,故该算法不满足“让权等待”,其他三个原则均满足

bool flag[2];	//表示是否想要进入临界区
//初始值 flag={false,false} 进程0和进程1均不想进入临界区
int turn = 0;	//优先让进程0进入临界区
//p0进程
flag[0] = true;	//进程0想要进入临界区	//进入区
turn = 1;	//优先让进程1进入临界区(谦让意味)	//进入区
while(flag[1] && turn == 1);	//检查进程1是否已经进入临界区
//如果上述不满足,代表 flag[1]=false 进程1不愿进入临界区
critical section;	//进程0进入临界区
flag[0] = false;	//退出区,访问完临界区后表示不再想进入临界区
remainder section;	//剩余区

//P1进程
flag[1] = true;	//进程1想要进入临界区	//进入区
turn = 0;	//优先让进程0进入临界区(谦让意味)	//进入区
while(flag[0] && turn == 0);	//检查进程0是否有意愿进入临界区,再次检查turn是否为0(防止进程在执行完该句的上一句就切换到进程0,从而使得turn值为1)
//如果上述不满足,代表 flag[0]=false 进程0不愿进入临界区
critical section;	//进程1进入临界区
flag[1] = false;	//退出区,访问完临界区后表示不再想进入临界区
remainder section;	//剩余区
1.4.2.2 实现进程互斥的硬件方法
1.4.2.2.1 中断屏蔽方法

利用“关中断指令”和“开中断指令”(与原语的实现相同)执行关中断指令后,CPU不再检查中断信号,后序指令不可能被中断(也就不可能在此期间发生进程切换)此时进程进入临界区,直到进程访问完临界区后,执行“开中断指令”

关中断指令
临界区
开中断指令

1.4.2.2.2 硬件指令方法

TestAndSet(TS指令/TSL指令)用硬件逻辑直接实现,执行过程不允许被中断
while不断检测临界区是否上锁了,如果函数返回值为false,则while不再循环,进程可以进入临界区,反之,进程无法进入临界区

TestAndSet将“检查”和“上锁”一起完成
暂时无法进入临界区的进程会占用CPU并一直判断while中的内容,违背了“让权等待”

//lock=true;表示临界区已上锁,其他进程无法进入
//lock=false;表示临界区未上锁,进程可以进入
bool TestAndSet(boolean *lock){
	boolean old; 	
	old = *lock;	//若lock为false,则old为false,则while不再执行,进程可以进入临界区
	*lock = true;	//进程进入临界区,将临界区上锁
	return old;		//若lock为false,返回old=false
}
//使用TSL实现进程互斥
//进程P
while(TestAndSet(&lock));	//若函数返回值为false,则while不再执行,进程可以进入临界区
critical section;	//临界区
lock = false;		//退出区
remainder section;	//剩余区

Swap指令(XCHG指令)

Swap(boolean *a,boolean *b){
	boolean temp;
	temp = *a;
	*a = *b;
	*b = temp;
}

boolean old = true;
//old == true 代表有进程正在访问临界区
while(old == true)	//循环检查,一旦old为false,循环不再执行,进程进入临界区
	Swap(&lock,&old);	//将old值与lock值进行交换
critical section;	//临界区
lock = false;		//退出区
remainder section;	//剩余区
1.4.2.3 信号量机制实现进程互斥(PV操作)
//记录型信号量的定义
typedef struct{
	int value;	//剩余资源数
	struct process *L;	//等待队列
} semaphore;

P和V操作必须成对存在,否则会出现永远无法唤醒进程的情况

//信号量机制实现互斥
semaphore mutex=1;//表示进入临界区的“入场券”

P1(){
	...
	P(mutex);	//若进入临界区的“入场券”没有了即mutex≤0,进程会进入阻塞态
	critical section;	//若有进程进入临界区
	V(mutex);
	...
}

P2(){
	...
	P(mutex);
	critical section;
	V(mutex);
	...
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Uncertainty!!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值