死锁
死锁的概念
什么是死锁
在并发环境下,各进程因竞争资源而造成的一种互相等待对方手里的资源,导致各个进程都阻塞,都无法向前推进的现象,就是“死锁”,发生死锁后若无外力干涉,这些进程都将无法向前推进。
例如哲学家进餐问题中,每一位哲学家都因为饥饿拿起了左边的筷子,却又都在等待右边的筷子,将会因无筷子可拿而无期限地等待,每个进程都占有一个资源,同时又在等待另一个进程拿到的资源,从而产生死锁问题。
进程死锁、饥饿、死循环的区别
- 死锁:互相等待对方手里的资源,导致各个进程都阻塞,都无法向前推进的现象
- 饥饿:由于长期得不到想要的资源,某进程无法向前推进的现象。
- 死循环:某进程执行过程中一直跳不出某个循环的现象。有时是因为程序逻辑bug导致,有时是程序员刻意为之
死锁产生的必要条件
产生死锁必须同时具备下面四个条件,缺一不可
- 互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁,一段时间内,某进程只能被一个进程占用。如果此时还有其他进程请求该资源,则请求进程只能等待,直至占有该资源的进程用毕释放。
- 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
- 不可抢占条件:进程已获得的资源在未使用完之前不能被抢占,只能在进程使用完时由自己释放
- 循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。
发生死锁时一定有循环等待,但是发生循环等待是未必死锁,除非系统中每类资源都只有一个。
什么时候会发生死锁
对不可剥夺资源的不合理分配,可能导致死锁。
死锁的处理策略
- 预防死锁:破坏产生死锁四个必要条件中的一个或几个
- 避免死锁:用某种方法防止系统进入不安全状态(银行家算法)
- 检测死锁:允许死锁发生,但可以通过检测机构及时地检测出死锁的发生,然后采取适当的措施解除死锁。
- 解除死锁:常用方法是撤销一些进程,回收他们的资源,将他们分配给已处于阻塞状态的进程。
死锁处理策略——预防死锁
预防死锁是指:破坏产生死锁四个必要条件中的一个或几个。但由于互斥条件是非共享设备所必须的,不仅不能改变,还应当加以保证,因此主要是破坏产生死锁的后三个条件。
破坏互斥条件
例如操作系统可以采用SPOOLing技术把独占设备在逻辑上改造成共享设备。但是不安全。
破坏不可抢占条件
- 某个进程请求新的资源得不到满足时,就必须释放已经持有的资源
- 某个进程请求新的资源得不到满足时,由操作系统协助,强行剥夺需要的资源
问题:
- 实现复杂,需付出很大的代价
- 可能会造成进程前一段工作失效,使进程前后两次运行信息不连续
- 延长进程周转时间,增加系统开销,降低了系统吞吐量
- 第一种方法可能因为反复申请和释放资源导致进程饥饿
破坏请求和保持条件
静态分配方法:
进程在运行前一次性地申请所需要的所有资源,若不能满足,则不运行,一旦运行,对所需要的资源保持不放。
- 优点:
简单、易行、安全 - 缺点:
资源严重浪费,利用率极低;
可能导经常致某些进程饥饿。
静态分配方法改进:
允许一个进程只获得运行初期所需的资源后,便开始运行。运行过程中再逐步释放已分配给自己的、已经用毕的资源,然后再请求新的所需资源。
破坏循环等待条件
顺序资源分配法:
给系统中的资源编号,规定每个进程必须按编号递增的顺序来请求资源,同类资源(编号相同)需一次申请完,如果某进程已经请求到了一些序号较高的资源,而之后又想请求序号较低的资源,必须先释放所有具有相同和更高序号的资源后,才能申请序号低的资源。
通常根据大多数进程需要资源的先后顺序来确定资源序号,比如输入设备规定较低序号,而输出设备规定较高序号。
- 优点:
资源利用率和系统吞吐量有较明显改善 - 缺点:
不方便增加新设备,可能需要重新分配编号
进程实际使用资源顺序和编号递增顺序若不一致,会导致资源浪费
必须按规定次序申请资源,用户编程麻烦
死锁处理策略——避免死锁
避免死锁方法中,把系统分为安全状态和不安全状态,可以在资源分配之前先预判这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求。
安全状态与不安全状态
指系统能按照某种进程推进顺序为每个进程分配其所需要的资源,直至满足每个进程对资源的最大需求,使每个进程都可以顺利地完成。安全状态可以有多个。
如果系统不能找到这样的一个安全序列,则称系统处于不安全状态。
如果系统处于安全状态,就一定不会发生死锁,如果系统处于不安全状态,就可能发生死锁。
银行家算法
假设你是银行家,你手头有固定的资金,有不同的用户向你提出贷款请求,但是这些人都是大哥,如果你借给他们的钱不能达到他们提出的最大需求,那么之前借的钱也不还给你了,因此每一次放贷你都要确保不会出现对方资金不还,你没有资金借给下一个人的情况。
例子:
- 核心思想:在进程提出资源申请时,先预判此次分配是否会导致系统进入不安全状态,如果会进入不安全状态,就暂时不答应这次请求,让该进程先阻塞等等
安全性算法
选自《计算机操作系统》内容
算法思想:相当于循环遍历所有进程,找到一个安全序列,若能找到,则可以继续执行,反之则不行
- 设置两个向量:
Work = Available;
:工作向量,表示系统可提供给进程继续运行所需的各类资源数目,含有m个元素。
Finish[i] = false;
:表示系统是否有足够的资源分配给第i个进程,使之运行完成 - 从进程集合中找到一个能满足下述条件的进程:
Finsih[i] = false;
Need[i,j] <= Work[j];
若找到,执行步骤3,若没找到,则执行步骤4
3. 当进程获得Pi资源后,可顺利执行,直至完成,并释放资源:
Work[j] = Work[j] + Allocation[i,j]; //将系统可分配资源加上进程i释放资源
Finish[i] = true; //进程设置为完成状态
go to step 2; //去第二步
- 如果所有进程的
Finsih[i]
都满足,则表示系统处于安全状态,否则处于不安全状态
死锁处理策略——死锁的检测与解除
死锁的检测
为了能对系统是否已发生了死锁进行检测,必须:
①用某种数据结构来保存资源的请求和分配信息
②提供一种算法,利用上述信息来检测系统是否已进入死锁状态
死锁定理:当且仅当S状态的资源分配图是不可完全简化的,该充分条件被称为死锁定理。
资源分配图
- 在资源分配图中,找出一个既不阻塞又不是孤点的进程节点Pi。顺利情况下,P可以获得所需资源运行直至运行完毕释放资源,这相当于消去Pi的请求边和分配边,使之成为孤立的节点
- 在进行一系列的化简之后,若消去图中所有的边,使所有的进程节点成为孤立节点,则称该图是可完全简化的,此时一定没有发生死锁(相当于能找到一个安全序列)
- 如果最终不能消除所有的边,则该图不可完全简化,那么此时就是发生了死锁,最终还连着边的那些进程就是属于死锁状态的进程。(如下图中,仅P3可以消边,则P1和P2都是处于死锁状态的进程)
死锁检测中的数据结构
类似于银行家算法中的数据结构:
- 可利用资源向量Available,表示了m类资源每一类资源可用的数目
- 把不占用资源的进程(向量Allocation = 0)记入L表中,即Li∪L
- 从进程集合中找到一个Request i <= Work 的进程,①资源分配图简化,释放出资源,增加工作向量Work = Work + Allocation i。②将它记入L表中。
- 若不能把所有的资源记入L表中,则系统状态S的资源分配图不可完全简化。系统状态将发生死锁。
Work = Available;
L = {L[i]|Allocation[i] = 0 ∩ Request[i] = 0} //无边孤立点
for(all Li∉L)
{
for(all Request[i]<=Work){
Work = Work + Alocation;
L[i]∪L;
}
}
deadlock=(L={P1,P2,...,Pn});
死锁的解除
- 抢占资源:从一个或多个进程中抢占足够数量的资源(挂起某些死锁进程),分配给其他死锁进程,以解除死锁状态,但是要防止被挂起的进程长时间得不到资源而饥饿。
- 终止或撤销进程:终止(或撤销)系统中的一个或多个死锁进程,直至打破循环环路,使系统从死锁状态中解脱出来。终止进程又包括两个方法:
①终止所有死锁进程:实现简单,但付出的代价可能会很大,被撤销的进程需要重新执行,而有一些可能已经执行很久快执行完毕。
②逐个终止进程:按照某种顺序,逐个终止进程,直到有足够的资源,相对温和,但付出代价也可能很大。每终止一个进程都需要做出选择策略,并在执行完毕之后进行死锁检测。
选择被终止进程的若干因素:
1.进程的优先级
2.进程已经执行多长时间,还需要执行多长时间
3.进程在运行中已经使用资源的多少,还需要多少
4.进程的性质是交互式还是批处理式(终止交互式进程影响用户体验) - 进程回退法:让一个或多个死锁进程回退到足以避免死锁的地步。这要求系统记录进程的历史信息,设置还原点。