挑战408——操作系统(13)——死锁(deadlock)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/redRnt/article/details/83412866

死锁,可以定义为一组竞争系统资源或者相互通信的进程,相互的“永久性“”的阻塞,若无外力作用,这组进程将永远没有办法继续执行。很遗憾,目前这种问题没有一种有效的通用解决方案。
这里对于死锁的定义理解要注意死锁的对象——一组进程,相互竞争。如果是一个进程可能长期或者永久性得不到执行,那么这个进程是处于饥饿状态而不是死锁。同时,饥饿 ≠死锁!,饥饿现象只是死锁的一种结果,就是说由于死锁的出现,导致了死锁的进程饿死。但是饥饿现象不是一定由于死锁导致的。比如前面的一些调度算法,也可能造成饥饿,因为有些程序可能永远得不到执行,但是不意味着它就死锁了。用句现在流行的话来说它仅仅是“自闭”了而已。

哲学家进餐问题

在讲PV操作的时候,特意没讲这个经典的问题,目的就是让学到这里的时候,对死锁再加深认识,直接举例子不如分析例子来的好。

问题描述:这个问题由伟大的计算机科学家dijkstra提出并解决。有5个哲学家,他们的生活方式是,思考,进餐。他们围绕着圆桌而坐,桌子上有5双筷子和5碗米饭,平时哲学家进行思考,饥饿的时候他拿起左右两边的筷子(一根一根的拿起),试图进餐,进餐完毕后又陷入思考。哲学家只有同时拿到筷子才能进食。如图:
在这里插入图片描述
我们按照之前的模板分析:

  1. 临界资源分析:显然筷子是临界资源,因为它一次只允许一个哲学家使用。
  2. 交互关系分析:这里有5个哲学家,也就是有5个一样的进程,他们对筷子的访问是互斥的。他们之间没有一定的先后顺序,毕竟不知道谁会先饿。
  3. 思路分析:哲学家能吃到饭的前提是,他手里有两根筷子,而拿到筷子的前提是,他左右两边的哲学家,恰好在思考。
  4. 信号量设置分析:这里筷子是临界资源,每个筷子都是一个临界资源,对临界资源的访问要以互斥的形式访问,所以要设置5个互斥信号量,信号量较多的情况下,我们用数组表示会方便很多,比如 chopsticks[5] = {1,1,1,1,1,}.因为没有同步关系,因此我们也不用设置其他的资源信号量。

伪代码如下:

//哲学家进餐问题
semaphore chopsticks[5] = {1,1,1,1,1};//定义互斥信号量数组
void philosopher(int i){ //第i个哲学家进程
	while (true){
		P(chopsticks[i]);//申请左边的筷子
		P(chopsticks[(i + 1)%5]);//申请右边的筷子
		.......
		..进餐.       //拿到了左右两根筷子后,进餐
		.......
		V(chopsticks[i]);//放下左边的筷子
		V(chopsticks[(i + 1)%5);//放下右边的筷子
		.......
		..思考.  //吃完饭思考
		.......
	}
}

上面的代码,解决了两个相邻的哲学家不会同时进餐的问题,但是思考这样一个问题。假设所有的哲学家同一时间都饿了呢?我们分析一下,进程同时执行下面的语句:

P(chopsticks[i]);申请左边的筷子

那么,现在所有的哲学家手里都有一根筷子,右边的筷子已经被别人拿走了,所以谁也没有办法进食,每个人都希望右边的人能放下手中的筷子,陷入了无休止的等待当中,哲学家迟早被饿死。这就是死锁现象。这样的理解很是深刻。
那么先就事论事,先解决这个问题再说:
提供一种思路,既然这样全都得饿死,那不如就先让一个人吃完,吃完后把筷子让出来。也就是说,只有当哲学家两边的筷子都能用的时候,才允许它吃饭。那么具体怎么实现呢?其实不难,我们只要在某个哲学家拿筷子的时候,其他的哲学家都不能拿筷子,也就是说取筷子这个动作是互斥的。于是有了下面的改进代码:

//哲学家进餐问题
semaphore chopsticks[5] = {1,1,1,1,1};//定义互斥信号量数组
semaphore mutex = 1;//设置使用筷子的互斥信号量
void philosopher(int i){ //第i个哲学家进程
	while (true){
		P(mutex);//申请使用筷子操作
		P(chopsticks[i]);//申请左边的筷子
		P(chopsticks[(i + 1)%5]);//申请右边的筷子
		.......
		..进餐.       //拿到了左右两根筷子后,进餐
		.......
		V(chopsticks[i]);//放下左边的筷子
		V(chopsticks[(i + 1)%5];//放下右边的筷子
		V(mutex);//筷子使用完毕,放回原位
		.......
		..思考.
		.......
	}
}

死锁产生的原因

死锁产生的原因主要有四个方面:资源不足,进程推进顺序非法,资源的使用。

  1. 资源不足:通常系统中拥有的不可剥夺的资源,数量不足以满足多个进程的需要,使得进程在运行过程中因为竞争资源而导致竞争资源陷入僵局,导致死锁。(比如竞争打印机,刚刚哲学家问题的筷子
  2. 进程推进顺序非法:进程中申请和释放资源的顺序安排不当(如将生产者消费者之间的PV操作的顺序颠倒)。如果进程P不是同时需要两个资源,先使用一个再申请一个,这样就不会产生死锁。
  3. 资源的使用。系统中的资源主要分两大类:可重复资源,可消耗资源。
    可重复资源包括:处理机,I/O通道,设备文件,以及数据库等等,这种资源,进程得到后,再释放,供其他进程再次使用。
    可消耗资源包括:中断,信号量,缓冲区。当一个进程使用该资源后,便不复存在。
    就生产者消费者问题而言,按照顺序先P(mutex)也是基于此考虑的。

解决死锁的方法是:预防,检测和避免。(这个很重要,常识)

死锁的必要条件

  1. 互斥。一段时间内,某种资源只能由一个进程占有,此时其他要求使用该资源的进程只能阻塞。
  2. 请求与保持。当进程已经占有了至少一个进程,又提出新的资源要求,而该被请求的资源又被其他进程占用,此时进程阻塞,但是对它持有的资源,保持不放。
  3. 不可剥夺。进程已经获得资源,在它使用完毕前不能被剥夺,只能使用完毕后自己释放。
  4. 环路条件。存在一个与资源的环形链,设链中的每个进程都在等待一个被占用的资源。(就是哲学家问题中的,每个哲学家都手持一根筷子的情况)。
    在这里插入图片描述
    一般情况下,前面三种情况都是合理的。进程间的互斥,是为了保证再现性,请求与保持是正常的运行,对于一些资源,比如打印机,是不能剥夺使用的。
    123是死锁的必要条件,而4是123的潜在的结果。当前面的123某一种情况与4同时出现的时候,表明进程间发生了死锁

解决死锁的方法是:预防,检测和避免。(这个很重要,常识)

死锁的预防

死锁的预防可以从4个必要条件入手。

  • 对于互斥,这是程序并发必不可少的一种机制,无法避免
  • 对于请求与保持。出现问题的原因是因为,运行过程中自己的资源不够,而向外申请造成的。所以我们可以预先分配。即要求进程一次性请求完所有资源,若不能满足,就阻塞这个进程。直到它所有的资源都满足为止。这就要求预知进程运行所需要的总资源数。
    缺点:进程将会被延迟运行,资源被严重浪费。而且最重要的是,进程只有在运行的时候才知道需要哪些资源
  • 对于不可剥夺,最直接的方式就是剥夺阻塞进程的资源,但是代价太大,会导致进程无限期推迟运行。
  • 对于环路,那么破坏环路的条件。可以采用有序分配策略
    如果一个进程已经分配到了某种类型的资源,它接下来请求的资源,只能是那些排在这个资源之后的资源。(即按一定顺序来申请资源)

死锁的检测

死锁检测算法主要是检查系统中的进程是否有循环等待,将系统总的进程和资源的申请和分配描述成一幅有向图,称为资源分配图。通过检查有向图中是否有环来判断死锁的存在。
在这里插入图片描述

用方框代表资源,方框里面的小圆圈代表的是资源数,P1,P2是进程。
从进程出的边为请求边,从进程入的边为分配边
那么,采用拓扑排序的方式来判断图中是否有环。关于拓扑排序 看这个:数据结构——图(9)——拓扑排序与DFS

死锁定理:系统处于死锁状态等价于状态S的资源分配图是不可以被简化的

死锁的预防

银行家算法(下篇详细介绍)

猜你喜欢

转载自blog.csdn.net/redRnt/article/details/83412866
今日推荐