1.死锁的概念
1.1 什么是死锁
先来看下一个例子:连接两个地方之间有一座桥,这座桥很窄,一次只能容纳一辆车的通行,这时候,从桥的两侧分别各有一辆车上来,对于A车而言,它行走了桥的一段路(占有该桥的一部分资源),想要过桥到达另一端需要B车让开从而才可以行驶,这时出于等待状态,而B车,也行走了桥的一段路(占有该桥的一部分资源),想要过桥到达另一端也需要A车让开从而才可以行驶,这时也出于等待状态。A车和B车两者之间谁也不让路,都处于等待对方的状态,结果造成两边的车都不倒车,互相等待对方让出通路,但是谁也不让路,就会无休止地等下去。如果把车看做进程,占有桥的部分路程视为占有资源,则这种现象就是死锁。
死锁就是系统中某个进程提出资源申请后,使得若干进程在无外力作用下永远不能前进的状态,这种情况不仅浪费大量系统资源,甚至导致整个系统崩溃。
1.2 死锁产生原因
产生死锁的根本原因就是资源有限且操作不当。
像上述例子,桥的通道一次只能限制一辆的通行,如果桥的大小足够两辆车并行的话,那上述的情形也不会发生,就像系统提供的资源有限,这就引起了并发进程对资源的争夺。另一个原因就是进程的推进顺序,推进顺序就是指怎么把资源合理分配,先分配给哪些进程合适等,像上述例子,两车互不相让,只能无限等待下去。
1.3 产生死锁的必要条件
1.互斥等待。每个资源要么可用,要么被某一个进程占有(即不能同时被两个或两个以上进程占有)。就像上例桥的通道被A车,B车占着。
2.不可抢占条件。一个进程在未使用完所获得的资源之前,其他进程不能强行地抢占该资源,该资源只能由它的占有者进程自行释放。就像A车需要等待B车主动让出,不可以去强制B车让出,B车需要等待A车主动让出,也不能就直接冲过去把A车顶出它的通路。
3.占有且申请条件。已经占有某个资源的进程还可以申请新的资源。就像A车占有桥的部分通路,但是要顺利通行还需要部分通路。
4.环路等待条件。存在一个进程等待序列{P1,P2,...,Pn},其中P1等待P2所占有的某一资源,P2等待P3所占有的某一资源,......,而Pn等待P1所占有的某一资源,形成一个进程循环等待环。就像A车等待B车,B车等待A车。
死锁发生时,上述四个条件一定同时满足,如果其中任何一个条件不成立,死锁就不会发生。
2.对待死锁的策略
2.1 鸵鸟策略
就像现实生活中的鸵鸟一样,遇到事情时就把头埋进地里装作没发生。忽略死锁问题,把死锁当做永远不会发生的问题,但是,万一发生死锁这种策略就不合适了。
2.2 死锁的预防
死锁的预防可以通过破坏死锁的四个必要条件之一,从而破坏死锁的产生条件,故而可以预防死锁。
2.2.1 破坏互斥条件
如果说某一资源不被一个进程独占,而是可以供多个进程同时使用,就像上述中的桥梁,如果说足够大,允许多辆车同时运行,就可以避免产生上述的情况。但是呢,因为某些资源固有的属性就是独占性的,所以该方法不能预防死锁。
2.2.2 破坏占有且等待条件
只要禁止已占用资源的进程再等待其他资源,就可以消除死锁了。一种实现方法是预分配资源,规定所有进程在开始执行之前就申请所需的全部资源,如果能满足则将它们分配给这个进程,从而保证它在执行过程中不再申请另外的资源;另一种则是“空手”申请资源策略,每个进程仅在它不占有资源时才可以申请资源,即在申请其他资源时,必须释放当前分到的全部资源。
这两种方法也存在缺点:首先是在多数情况下,一个进程在执行之前不可能知道它所需的全部资源,另外会导致资源利用率低以及降低了进程的并发性,甚至会出现“饥饿”现象,即某些进程会一直得不到需要的资源从而一直没有运行的机会。
2.2.3 破坏非抢占条件
也就是说,一个进程想申请被其他进程占有的资源,这个进程出于等待状态,这时候,它所占有的其他资源可以被其他进程抢占。
2.2.4 破坏环路等待条件
实现资源有序分配策略,把全部资源事先按类编号,然后依序分配,使进程申请、占有资源时不会形成环路。进程占用了小号资源,才能申请大号资源,就不会产生环路,从而预防了死锁。
2.3 死锁的避免
这种方法的实现是在实施资源分配之前进行检查,若预测有发生死锁的可能,则加以避免。这种方法的关键是确定资源分配的安全性。
2.3.1.安全状态
如果在当前资源分配状态下没有能够发生死锁,并且即使所有进程突然都提出对资源的最大需求,也仍然存在某种调度序列能够使它们依次成功运行完毕,则称该状态时安全的,相应的进程调度序列就是安全序列了,若系统中不存在这样的一个安全序列,则该状态则是不安全的。比如说,假设一个工厂里有十台设备,有三个车间,分别拥有3台、2台和2台设备了,而他们工作所需要的设备数目是9、4和7,这时候工厂除了分配给他们的设备,还有3台空闲的,我们通过一个合理的分配手段,首先这三个车间还需要的设备数目分别是6、2、5,先分配2台空闲的给2号车间,2号空间完成任务之后空出他们的设备,加上刚才剩余的一台,则现在有5台空闲设备,再把这5台空闲设备分配给3号车间,三号车间完成它们的任务之后也空出他们的设备,则这时候空闲设备有7台了,把这些分配给1号车间完成任务,这里有个分配设备的顺序,2号车间-->3号车间-->1号车间,这个就是一个安全序列了,也就是说这里存在一个安全状态了。
1.死锁状态是不安全状态
2.如果处于不安全状态,并不意味着它就在死锁状态,而是表示存在导致死锁的危险
3.如果一个进程申请的资源当前是可用的,但为了避免死锁,该进程也可能必须等待,此时资源利用率会下降。
2.3.2 银行家算法
这个是由Dijstra首先提出并加以解决的一个避免死锁的算法。
银行家算法的设计思想:当用户申请一组资源时,系统必须做出判断——如果把这些资源分出去,系统是否还处于安全状态,若是,就可以分配这些资源,否则,该申请暂不予满足。
其实也就是说找到一个安全序列,系统根据该安全序列,依次把资源分配给进程并执行下去。
[背景知识]
一个银行家如何将一定数目的资金安全地借给若干个客户,使这些客户既能借到钱完成要干的事,同时银行家又能收回全部资金而不至于破产,这就是银行家问题。这个问题同操作系统中资源分配问题十分相似:银行家就像一个操作系统,客户就像运行的进程,银行家的资金就是系统的资源。
[问题的描述]
一个银行家拥有一定数量的资金,有若干个客户要贷款。每个客户须在一开始就声明他所需贷款的总额。若该客户贷款总额不超过银行家的资金总数,银行家可以接收客户的要求。客户贷款是以每次一个资金单位(如1万RMB等)的方式进行的,客户在借满所需的全部单位款额之前可能会等待,但银行家须保证这种等待是有限的,可完成的。
例如:有三个客户C1,C2,C3,向银行家借款,该银行家的资金总额为10个资金单位,其中C1客户要借9各资金单位,C2客户要借3个资金单位,C3客户要借8个资金单位,总计20个资金单位。某一时刻的状态如图所示。
对于a图的状态,按照安全序列的要求,我们选的第一个客户应满足该客户所需的贷款小于等于银行家当前所剩余的钱款,可以看出只有C2客户能被满足:C2客户需1个资金单位,小银行家手中的2个资金单位,于是银行家把1个资金单位借给C2客户,使之完成工作并归还所借的3个资金单位的钱,进入b图。同理,银行家把4个资金单位借给C3客户,使其完成工作,在c图中,只剩一个客户C1,它需7个资金单位,这时银行家有8个资金单位,所以C1也能顺利借到钱并完成工作。最后(见图d)银行家收回全部10个资金单位,保证不赔本。那麽客户序列{C1,C2,C3}就是个安全序列,按照这个序列贷款,银行家才是安全的。否则的话,若在图b状态时,银行家把手中的4个资金单位借给了C1,则出现不安全状态:这时C1,C3均不能完成工作,而银行家手中又没有钱了,系统陷入僵持局面,银行家也不能收回投资。
综上所述,银行家算法是从当前状态出发,逐个按安全序列检查各客户谁能完成其工作,然后假定其完成工作且归还全部贷款,再进而检查下一个能完成工作的客户,......。如果所有客户都能完成工作,则找到一个安全序列,银行家才是安全的。
银行家算法允许存在死锁必要条件中的互斥条件,占有且申请条件,不可抢占条件,这样,它与预防死锁的几种方法相比较,限制条件少了,资源利用程度提高了。这是该算法的优点。
但是也存在该算法的缺点:
〈1〉这个算法要求客户数保持固定不变,这在多道程序系统中是难以做到的。
〈2〉这个算法保证所有客户在有限的时间内得到满足,但实时客户要求快速响应,所以要考虑这个因素。
〈3〉由于要寻找一个安全序列,实际上增加了系统的开销。
2.4 死锁的检测和恢复
指系统设有专门的机构,当死锁发生时,该机构能检测到死锁发生的位置和原因,并且通过外力破坏死锁发生的必要条件,从而使并发进程从死锁状态中解脱出来。
2.4.1 对单体资源类的死锁检测
对资源分配图进行变形为等待图,在等待图中,从pi到pj的边表示己进程pi正等待pj释放它所需的资源,当且仅当等待图中有环路时,系统存在死锁。
2.4.2 对多体资源类的死锁检测
针对多体资源类,可以采用死锁检测算法,当然只是简单地调查尚待完成的各个进程所有可能的分配序列。这种算法就是找到一个进程,它申请的资源可以被可用资源满足,就假定这个进程可以得到所需的资源,它运行下去,直至完成,然后释放所占有的全部资源,接着查找是否有其他的进程也满足这样的,否则就检测出死锁。
2.4.3 从死锁中恢复
当检测到死锁之后,必须采取某种措施使系统从死锁中解脱出来。
1.通过抢占资源恢复。
临时性地把资源从当前占有它的进程那里拿过来,分给另外的某些进程,直至死锁环路被破坏。
2.通过回退执行实现恢复。
就是理解为一个备份状态,定期对系统各个进程进行检查,并将检查点的有关信息写入文件,以备重启时使用。当检测到死锁时,就让某个占有必要资源的进程回退到它取得另外某个资源之前的一个检查点,回退过程中释放的资源分配个另一个死锁进程,然后重新启动执行。
3.通过杀掉进程实现恢复。
强行终止进程,系统可以从这些进程回收它们占有的全部资源,然后分给其他等待这些资源的进程。
(1)终止所有的死锁进程。这种方法必然会打破死锁环路,但是代价太高,因为这些进程可能已经计算了很长一段时间了,终止之后需要从头开始。
(2)一次终止一个进程,直至消除死锁环路。每当终止一个进程之后,必须调用死锁检测算法,以确定是否还有别的进程仍在死锁状态。
当进程长时间得不到所需资源,使进程难以推进时,我们称发生了进程饥饿,若是长时间的处于饥饿状态导致后续已没有完成该进程任务的必要时,我们就称这个进程饿死了。就像我们在餐馆里点完餐之后却迟迟不上,而别的桌都已经吃饱走了。