调研死锁知识

来源:互联网 发布:墨子号成功发射知乎 编辑:程序博客网 时间:2024/06/18 17:22

参考:http://blog.163.com/yanenshun@126/blog/static/128388169200982444858590/?fromdm&fromSearch&isFromSearchEngine=yes

(一)死锁概念

(1),背景知识
首先该明白什么叫同步与互斥以及什么是互斥锁?
同步:
按照特定顺序访问临界资源,在互斥基础上实现。
互斥:对临界资源的访问时串行式的。每次只能有一个线程访问。
对于多个线程的访问,访问冲突的问题是很普遍的,解决的办法是引入互斥锁,获得锁的线程可以完成“读-修改-写”三步操作组成原子操作,要么都执行,要么不执行,不会执行的中间被打断,也不会再其他处理器上并行做这个操作。
互斥锁本身也是临界资源,如何保护这份临界资源呢,必须保证加锁和解锁是原子性的,互斥锁相当于二元信号量,使用场景不同,二元信号量适用进程,互斥锁适用线程,进程1申请锁成功,但是被切出去,但它是抱着锁出去的(可以这么通俗理解),进程2申请不到锁,只有等进程1在切回来,完成它的操作,然后释放锁,这样就保证了对数据的操作是原子性的了。

(2)死锁概念:
死锁:是指两个或者两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。

(二) 产生场景及其原因

场景一:
如果一个线程先后两次申请锁,在第二次申请时,由于锁已经被占用,该线程会被挂起等待别的线程释放锁,然而锁正是被自己占用着,该线程又被挂起而没有机会释放锁,因此永远处于挂起等待状态,造成死锁。

场景二:
线程1获得锁1,线程2获得锁2,这时线程1调用lock试图获得锁2,结果是线程1需要挂起,等待线程2释放锁2,而这时线程2也调用lock试图获得锁1,结果线程2也要挂起,等待线程1释放锁1,这样两个线程都处于挂起等待状态,造成锁1和锁2不能释放。造成死锁问题。

(三) 死锁产生四个必要条件

①、互斥。
(一次只有一个进程可以使用一个资源,其他进程不能访问已分配给其他进程的资源)。

②、占有且等待。(请求且保持)
当一个进程等待其他进程时(其他资源),继续占有已经分配的资源。

③、不可抢占。
不能强行抢占进程已占有的资源。
(当一个进程的优先级高于一个占有资源的进程的优先级时,并不能强行抢占)。
前三个条件都只是死锁存在的必要条件,但不是充分条件。对死锁的产生,还需要第四个条件。

④、循环等待。(取决于涉及到的进程请求和释放资源的顺序)
存在一个封闭的进程链,使得每个进程至少占有此链中下一个进程所需要的一个资源。
这里写图片描述

(四)死锁避免算法有哪些,原理是什么

前面介绍了死锁发生时的四个必要条件,只要破坏这四个必要条件中的任意一个条件,死锁就不会发生。这就为我们解决死锁问题提供了可能。一般地,解决死锁的方法分为死锁的预防,避免,检测与恢复三种(注意:死锁的检测与恢复是一个方法)。我们将在下面分别加以介绍。

死锁的预防是保证系统不进入死锁状态的一种策略。它的基本思想是要求进程申请资源时遵循某种协议,从而打破产生死锁的四个必要条件中的一个或几个,保证系统不会进入死锁状态。

〈1〉打破互斥条件。即允许进程同时访问某些资源。但是,有的资源是不允许被同时访问的,像打印机等等,这是由资源本身的属性所决定的。所以,这种办法并无实用价值。

〈2〉打破不可抢占条件。即允许进程强行从占有者那里夺取某些资源。就是说,当一个进程已占有了某些资源,它又申请新的资源,但不能立即被满足时,它必须释放所占有的全部资源,以后再重新申请。它所释放的资源可以分配给其它进程。这就相当于该进程占有的资源被隐蔽地强占了。这种预防死锁的方法实现起来困难,会降低系统性能。

〈3〉打破占有且申请条件。可以实行资源预先分配策略。即进程在运行前一次性地向系统申请它所需要的全部资源。如果某个进程所需的全部资源得不到满足,则不分配任何资源,此进程暂不运行。只有当系统能够满足当前进程的全部资源需求时,才一次性地将所申请的资源全部分配给该进程。由于运行的进程已占有了它所需的全部资源,所以不会发生占有资源又申请资源的现象,因此不会发生死锁。但是,这种策略也有如下缺点:

(1)在许多情况下,一个进程在执行之前不可能知道它所需要的全部资源。这是由于进程在执行时是动态的,不可预测的;

(2)资源利用率低。无论所分资源何时用到,一个进程只有在占有所需的全部资源后才能执行。即使有些资源最后才被该进程用到一次,但该进程在生存期间却一直占有它们,造成长期占着不用的状况。这显然是一种极大的资源浪费;

(3)降低了进程的并发性。因为资源有限,又加上存在浪费,能分配到所需全部资源的进程个数就必然少了。

(4)打破循环等待条件,实行资源有序分配策略。采用这种策略,即把资源事先分类编号,按号分配,使进程在申请,占用资源时不会形成环路。所有进程对资源的请求必须严格按资源序号递增的顺序提出。进程占用了小号资源,才能申请大号资源,就不会产生环路,从而预防了死锁。这种策略与前面的策略相比,资源的利用率和系统吞吐量都有很大提高,但是也存在以下缺点:

(1)限制了进程对资源的请求,同时给系统中所有资源合理编号也是件困难事,并增加了系统开销;

(2)为了遵循按编号申请的次序,暂不使用的资源也需要提前申请,从而增加了进程对资源的占用时间。

1.安全序列

我们首先引入安全序列的定义:所谓系统是安全的,是指系统中的所有进程能够按照某一种次序分配资源,并且依次地运行完毕,这种进程序列{P1,P2,…,Pn}就是安全序列。如果存在这样一个安全序列,则系统是安全的;如果系统不存在这样一个安全序列,则系统是不安全的。

安全序列{P1,P2,…,Pn}是这样组成的:若对于每一个进程Pi,它需要的附加资源可以被系统中当前可用资源加上所有进程Pj当前占有资源之和所满足,则{P1,P2,…,Pn}为一个安全序列,这时系统处于安全状态,不会进入死锁状态。  

虽然存在安全序列时一定不会有死锁发生,但是系统进入不安全状态(四个死锁的必要条件同时发生)也未必会产生死锁。当然,产生死锁后,系统一定处于不安全状态。

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〉由于要寻找一个安全序列,实际上增加了系统的开销。

8.4 死锁的检测与恢复

一般来说,由于操作系统有并发,共享以及随机性等特点,通过预防和避免的手段达到排除死锁的目的是很困难的。这需要较大的系统开销,而且不能充分利用资源。为此,一种简便的方法是系统为进程分配资源时,不采取任何限制性措施,但是提供了检测和解脱死锁的手段:能发现死锁并从死锁状态中恢复出来。因此,在实际的操作系统中往往采用死锁的检测与恢复方法来排除死锁。

死锁检测与恢复是指系统设有专门的机构,当死锁发生时,该机构能够检测到死锁发生的位置和原因,并能通过外力破坏死锁发生的必要条件,从而使得并发进程从死锁状态中恢复出来。

1.放大观看>>)

图中所示为一个小的死锁的例子。这时进程P1占有资源R1而申请资源R2,进程P2占有资源R2而申请资源R1,按循环等待条件,进程和资源形成了环路,所以系统是死锁状态。进程P1,P2是参与死锁的进程。

下面我们再来看一看死锁检测算法。算法使用的数据结构是如下这些:

  占有矩阵A:n*m阶,其中n表示并发进程的个数,m表示系统的各类资源的个数,这个矩阵记录了每一个进程当前占有各个资源类中资源的个数。   申请矩阵R:n*m阶,其中n表示并发进程的个数,m表示系统的各类资源的个数,这个矩阵记录了每一个进程当前要完成工作需要申请的各个资源类中资源的个数。   空闲向量T:记录当前m个资源类中空闲资源的个数。   完成向量F:布尔型向量值为真(true)或假(false),记录当前n个并发进程能否进行完。为真即能进行完,为假则不能进行完。   临时向量W:开始时W:=T。

算法步骤:

 (1)W:=T, 对于所有的i=1,2,...,n, 如果A[i]=0,则F[i]:=true;否则,F[i]:=false (2)找满足下面条件的下标i: F[i]:=false并且R[i]〈=W 如果不存在满足上面的条件i,则转到步骤(4)。 (3)W:=W+A[i] F[i]:=true 转到步骤(2) (4)如果存在i,F[i]:=false,则系统处于死锁状态,且Pi进程参与了死锁。什麽时候进行死锁的检测取决于死锁发生的频率。如果死锁发生的频率高,那麽死锁检测的频率也要相应提高,这样一方面可以提高系统资源的利用率,一方面可以避免更多的进程卷入死锁。如果进程申请资源不能满足就立刻进行检测,那麽每当死锁形成时即能被发现,这和死锁避免的算法相近,只是系统的开销较大。为了减小死锁检测带来的系统开销,一般采取每隔一段时间进行一次死锁检测,或者在CPU的利用率降低到某一数值时,进行死锁的检测。 

2.死锁的恢复

一旦在死锁检测时发现了死锁,就要消除死锁,使系统从死锁状态中恢复过来。

(1)最简单,最常用的方法就是进行系统的重新启动,不过这种方法代价很大,它意味着在这之前所有的进程已经完成的计算工作都将付之东流,包括参与死锁的那些进程,以及未参与死锁的进程。(2)撤消进程,剥夺资源。终止参与死锁的进程,收回它们占有的资源,从而解除死锁。这时又分两种情况:一次性撤消参与死锁的全部进程,剥夺全部资源;或者逐步撤消参与死锁的进程,逐步收回死锁进程占有的资源。一般来说,选择逐步撤消的进程时要按照一定的原则进行,目的是撤消那些代价最小的进程,比如按进程的优先级确定进程的代价;考虑进程运行时的代价和与此进程相关的外部作业的代价等因素。 

此外,还有进程回退策略,即让参与死锁的进程回退到没有发生死锁前某一点处,并由此点处继续执行,以求再次执行时不再发生死锁。虽然这是个较理想的办法,但是操作起来系统开销极大,要有堆栈这样的机构记录进程的每一步变化,以便今后的回退,有时这是无法做到的。