第二种技术是死锁的检测和恢复。这种解决方式不会尝试去阻止死锁的出现。相反,这种解决方案会希望死锁尽可能的出现,在监测到死锁出现后,对其进行恢复。下面我们就来探讨一下死锁的检测和恢复的几种方式
每种类型一个资源的死锁检测方式
每种资源类型都有一个资源是什么意思?我们经常提到的打印机就是这样的,资源只有打印机,但是设备都不会超过一个。
可以通过构造一张资源分配表来检测这种错误,比如我们上面提到的
如果这张图包含了-一个或-一个以上的环,那么死锁就存在,处于这个环中任意-个进程都是死锁的进程。
每种类型多个资源的死锁检测方式
如果有多种相同的资源存在,就需要采用另-种方法来检测死锁。可以通过构造一个矩阵来检测从 P1 ->Pn这n个进程中的死锁。
现在我们提供- -种基于矩阵的算法来检测从P1到Pn这n个进程中的死锁。假设资源类型为m, E1代表资源类型1,E2表示资源类型2,Ei 代表资源类型i(1 <=i<= m)。E表示的是现有资源向量(existing resource vector) ,代表每种已存在的资源总数。
现在我们就需要构造两个数组: C表示的是当前分配矩阵(current allocation matrix), R表示的是请求矩阵(request matrix) 。Ci 表示的是Pi持有每-种类型资源的资源数。所以,Cij 表示Pi持有资源j的数量。Rij表示Pi所需要获得的资源j的数量
一般来说,已分配资源j的数量加起来再和所有可供使用的资源数相加=该类资源的总数。
死锁的检测就是基于向量的比较。每个进程起初都是没有被标记过的,算法会开始对进程做标记,进程被标记后说明进程被执行了,不会进入死锁,当算法结束时,任何没有被标记过的进程都会被判定为死锁进程。
上面我们探讨了两种检测死锁的方式,那么现在你知道怎么检测后,你何时去做死锁检测呢? - -般来说,有两个考量标准:
每当有资源请求时就去检测,这种方式会占用昂贵的CPU时间。
每隔k分钟检测一次, 或者当CPU使用率降低到某个标准下去检测。考虑到CPU效率的原因,如果死锁进程达到一定数量,就没有多少进程可以运行,所以CPU会经常空闲。
从死锁中恢复
上面我们探讨了如何检测进程死锁,我们最终的目的肯定是想让程序能够正常的运行下去,所以针对检测出来的死锁,我们要对其进行恢复,下面我们会探讨几种死锁的恢复方式
通过抢占进行恢复
在某些情况下,可能会临时将某个资源从它的持有者转移到另一个进程。比如在不通知原进程的情况下,将某个资源从进程中强制取走给其他进程使用,使用完后又送回。这种恢复方式一般比较困难而且有些简单粗暴,并不可取。
通过回滚进行恢复
如果系统设计者和机器操作员知道有可能发生死锁,那么就可以定期检查流程。进程的检测点意味着进程的状态可以被写入到文件以便后面进行恢复。检测点不仅包含存储映像(memory image) ,还包含资源状态(resource state) 。-种更有效的解决方式是不要覆盖原有的检测点,而是每出现-一个检测点都要把它写入到文件中,这样当进程执行时,就会有-系列的检查点文件被累积起来。
为了进行恢复,要从上一个较早的检查点上开始,这样所需要资源的进程会回滚到.上一个时间点,在这个时间点上,死锁进程还没有获取所需要的资源,可以在此时对其进行资源分配。
杀死进程恢复
最简单有效的解决方案是直接杀死一一个死锁进程。但是杀死-一个进程可能照样行不通,这时候就需要杀死别的资源进行恢复。
另外一种方式是选择一个环外的进程作 为牺牲品来释放进程资源。
我们上面讨论的是如何检测出现死锁和如何恢复死锁,下面我们探讨几种规避死锁的方式
单个资源的银行家算法
银行家算法是Dijkstra在1965年提出的一种调度算法,它本身是一种死锁的调度算法。它的模型是基于一个城镇中的银行家,银行家向城镇中的客户承诺了一定数量的贷款额度。算法要做的就是判断请求是否会进入一种不安全的状态。如果是,就拒绝请求,如果请求后系统是安全的,就接受该请求。
比如下面的例子,银行家-共为所有城镇居民提供了15单位个贷款额度,一个单位表示1k美元,如下所示
城镇居民都喜欢做生意,所以就会涉及到贷款,每个人能贷款的最大额度不一样,在某- -时刻,A/B/C/D的贷款金额如下
上面每个人的贷款总额加起来是13,马上接近15,银行家只能给A和C进行放贷,可以拖着B和D、所以,可以让A和C首先完成,释放贷款额度,以此来满足其他居民的贷款。这是一种安全的状态。
如果每个人的请求导致总额会超过甚至接近15,就会处于一种不安全的状态,如下所示
这样,每个人还能贷款至少2个单位的额度,如果其中有一个人发起 最大额度的贷款请求,就会使系统处于一种死锁状态。
这里注意一点:不安全状态并不一定引起死锁,由于客户不一定需要其最大的贷款额度,但是银行家不敢抱着这种侥幸心理。
银行家算法就是对每个请求进行检查,检查是否请求会引起不安全状态,如果不会引起,那么就接受该请求;如果会引起,那么就推迟该请求。
类似的,还有多个资源的银行家算法,读者可以自行了解。
死锁本质.上是无法避免的,因为它需要获得未知的资源和请求,但是死锁是满足四个条件后才出现的,它们分别是
互斥
保持和等待
不可抢占
循环等待
我们分别对这四个条件进行讨论,按理说破坏其中的任意-个条件就能够破坏死锁
破坏互斥条件
我们首先考虑的就是破坏互斥使用条件。如果资源不被一个进程独占, 那么死锁肯定不会产生。如果两个打印机同时使用一个资源会造成混乱,打印机的解决方式是使用假脱机打印机(spoolingprinter),这项技术可以允许多个进程同时产生输出,在这种模型中,实际请求打印机的唯一进程是打印机守护进程,也称为后台进程。后台进程不会请求其他资源。我们可以消除打印机的死锁。后台进程通常被编写为能够输出完整的文件后才能打印,假如两个进程都占用了假脱机空间的一半,而这两个进程都没有完成全部的输出,就会导致死锁。
因此,尽量做到尽可能少的进程可以请求资源。
破坏保持等待的条件
第二种方式是如果我们能阻止持有资源的进程请求其他资源,我们就能够消除死锁。- 种实现方式是让所有的进程开始执行前请求全部的资源。如果所需的资源可用,进程会完成资源的分配井运行到结束。
如果有任何-个资源处于频繁分配的情况,那么没有分配到资源的进程就会等待。
很多进程无法在执行完成前就知道到底需要多少资源,如果知道的话,就可以使用银行家算法;还有一个问题是这样无法合理有效利用资源。
还有一种方式是进程在请求其他资源时,先释放所占用的资源,然后再尝试一-次获取全部的资源。破坏不可抢占条件
破坏不可抢占条件也是可以的。可以通过虚拟化的方式来避免这种情况。
破坏循环等待条件
现在就剩最后一个条件了,循环等待条件可以通过多种方法来破坏。-种方式是制定-个标准,-个进程在任何时候只能使用一种资源。如果需要另外-种资源,必须释放当前资源。对于需要将大文件从磁带复制到打印机的过程,此限制是不可接受的。
另一种方式是将所有的资源统一编号,如下图所示
进程可以在任何时间提出请求,但是所有的请求都必须按照资源的顺序提出。如果按照此分配规则的话,那么资源分配之间不会出现环。
尽管通过这种方式来消除死锁,但是编号的顺序不可能让每个进程都会接受。