操作系统(七) -- 死锁

版权声明:若未特别声明,文章可随意转载,但是请注明出处~~ 文章若有侵权请在下方评论,博主看到后马上删除!! https://blog.csdn.net/williamgavin/article/details/83188411


多个进程在交替执行的时候,如果控制不好,就会出现死锁的情况;这个问题需要操作系统去处理。

死锁举例

如下例子:

Producer(item)
{
	P(mutex);		
	P(empty);

	// 代码

	V(mutex);
	V(full);
}

Consumer()
{
	P(mutex);
	P(full);

	// 代码

	V(mutex);
	V(empty);
}

一种可能的调用顺序如下:
当mutex=1,empty=0时,首先Producer(),然后Consumer;

mutex=1,empty=0;首先调用Producer(),P(empty)的时候发现empty已经为0,进入睡眠。这时候无论调用多少个
Producer进程都会进入睡眠;如果调用Consumer()进程,因为mutex为1,前面调用producer()的时候已经P(mutex)了,在Consumer中再次P(mutex),那么Consumer进程会睡眠;这时候无论来多少个Consumer都会睡眠。

那么Producer什么才能wakeup呢?当V(mutex)时,但是V(mutex)在Consumer中,而且Consumer进程会在P(mutex)的时候就睡眠。也就是Consumer持有Producer所需要的资源不释放,导致Producer无法执行。换个角度,
为什么Consumer会不执行呢?也是因为Producer持有Consumer资源不释放导致的。

死锁的基本概念

死锁的定义:

死锁是指由于两个或者多个进程互相持有对方所需要的资源,导致这些进程处于等待状态,无法前往执行。

死锁会造成什么后果呢?

CPU利用率下降,也就是电脑会变卡。

想要知道某种情况的解决方法肯定首先得知道这种情况的产生原因。

死锁的成因

  1. 资源是互斥的,一旦占有别人无法使用。
    比如某个Producer进程sleep了,无法V(mutex);其他的Producer进程不知绕过前面P(mutex)而直接执行V(mutex);

  2. 进程占有了资源,不释放就再去申请资源。
    一个Producer进程P(mutex)占有了mutex资源,但是没有V(mutex);其他的Producer进程仍然P(mutex),无法V(mutex)

死锁的四个必要条件

  1. 互斥使用
    资源的固有特性
  2. 不可抢占
    资源只能自愿放弃,无法抢占
  3. 请求和保持
    首先占有资源,不释放,再去申请资源
  4. 循环等待
    就像上面第一个例子,Producer和Consumer都持有对方的资源,但是都无法释放,构成一个循环。
    Producer的P(mutex)如果要继续执行,需要等待Consumer的V(mutex);Consumer的V(mutex)如果想继续执行,
    需要等待Consumer的P(mutex)继续执行;而Consumer如果想继续执行,那么就得等待Producer的V(mutex);而
    Producer的V(mutex)想继续执行,就得等待Producer的P(mutex);构成一个环路。

死锁的处理方法概述

1,死锁预防
2,死锁避免
3,死锁检查+恢复
4,死锁忽略

下面具体谈谈这四种方式

死锁的处理方法

死锁预防

这里介绍两种方式,
方式一:一次性申请所有需要的资源。这样就不会占有资源再去申请其他资源了。破坏了必要条件3。
但是这种方式存在很大的不足:
第一点:在程序运行前就要知道这个程序会申请多少资源,需要预知未来,变成困难
第二点:可能许多资源分配后很长时间后才使用,资源利用率低

方式二:资源按顺序申请,这种方式不会造成环路等待。假设有十个资源,申请的时候必须按照顺序申请,需要资源1就申请资源1,需要资源2就申请资源2,但是如果需要使用资源10,就必须将前面的资源都申请了;但是也会造成很大的资源浪费。

从这里也能看到,死锁预防的方法并不好。

死锁避免

加果系统中的所有进程存在一个可完成的执行序列P1…Pn,则称系统处于安全状态。
那这个安全序列怎么找到呢?使用银行家算法

Allocation Need Available
ABC ABC ABC
--------- ------------ ------------
PO 010 743 230
P1 302 020
P2 302 600
P3 211 010
P4 002 431

P0到P4表示五个进程,ABC表示三种资源,ABC下面的数字表示对应的资源个数,Allocation表示当前进程占用的资源,
Need表示该进程执行需要用的资源,Available表示系统剩余的资源。

银行家算法大意是:如果P1要执行,那么假设让P1执行一下,如果P1执行之后没有死锁,没问题;那就让P1执行。如果P1执行之后引起死锁了,那就不让P1执行。这就是死锁避免。

m 表示资源数量,n表示进程个数

int Available[1...m] ;//每种资源剩余数量
int Allocation [ 1...n, 1...m] ;//己分配资源数量
int Need [ 1..n, 1..ml;//进程还需的各种资源数量
int Work [ 1..m] ;//工作向量
bool Finish  [ 1..n] ;//进程是否结束

Work=Available;Finish[1..n]=false;
while(true){
    for (i=1;i<=n; i++)
	{
		if(Finish[i]==false && Need [i] <= Work[i] )
		{
			Work=Work+Allocation[i];
			Finish[i]=true;  break; 
		}
		else
		{
			goto end;
		}
    )
)
End:
	for (i=1;i<=n; i++)
	  	if(Finish[i]==false)return "deadlock";

银行家算法的时间复杂度是O(mn^2);如果每次申请资源都调用一次,那么系统的开销太大了。

死锁检测+恢复

因为银行家算法的效率太低了,所以采取检测+恢复的方式。定时检测或者资源利用率低的时候检测。但是检测到某些进程死锁之后如何处理呢?回滚?回滚需要机制的支持;而且如果是一些文件修改的进程呢?所以也不好处理。

死锁忽略

碰见死锁直接忽略,这才是windows和Linux采用的机制;因为死锁本来就是小概率事件,而且重启可以解决这个问题,对于个人PC机来说,一般也不会造成很大影响。

参考资料

哈工大李志军操作系统

猜你喜欢

转载自blog.csdn.net/williamgavin/article/details/83188411