1.什么是死锁
我们先看一个生活中的例子,有1、2、3、4共四条公路,现在A、B、C、D四辆车驶入成下图的情况。要让A成功向前行驶,就必须先让B通过;而B想要通过,就必须让C先通过;同理C想要通过,就必须先让D通过;D想要通过,就必须让A先通过。最后造成每辆车都想要通过但有必须自己先通过的死循环
。
我们将上图中的公路看成计算机的资源,将车看成在CPU中运行的进程,就可以得到死锁的概念。我们将这种多个进程由于互相等待对方持有的资源而造成谁都无法执行的情况叫死锁。一旦出现死锁,在CPU中运行的进程就会因为缺少资源而进行不下去,CPU就会一直阻塞,导致计算机没有程序可执行,计算机就不工作了,CPU利用率非常低。所以就有必要解决这个问题。
2.死锁的成因
- 资源
互斥
使用,一旦占有别人就无法使用,除非自己主动释放。 - 进程占有了一些资源,又不主动释放,而又想再去申请别的资源。
- 各自占有的资源和互相申请的资源造成了
环路等待
。
3.死锁的必要条件
互斥使用
。资源的固有特性,如道路路口。不可抢占
。资源只能资源放弃,如车开走以后。请求和保持
。进程必须占有资源的同时,再去申请资源。循环等待
。在资源分配图中存在一条环路。
4.死锁的处理方法
死锁预防
。破坏死锁产生的条件。将死锁比作现实生活中的火灾,则死锁预防就相当于预防火灾。死锁避免
。检测每个资源请求,如果会造成死锁就拒绝。例如煤气报警器检测到煤气超标时,自动切断电源。死锁的检测+恢复
。检测到死锁出现时,让一些进程回滚,让出资源。例如发生火灾时立即拿出灭火器。更形象的例子比如发生堵车时,让后面的车后退,让出道路。死锁忽略
。就好像没有发生死锁一样,不用关心。例如太阳上发生火灾就可以全然不顾。
5.处理死锁的例子
- 死锁预防的例子
- 在进程执行前,一次性申请所有需要的资源。这样一来就不用占有资源的同时再去申请资源。
缺点1:需要预知未来,编程困难。
缺点2:许多资源分配很长时间后才使用,资源利用率低。 - 对资源类型进行排序,资源申请必须按序进行,不会出现环路等待。
缺点:仍然会造成资源浪费。
- 死锁避免的例子
判断此次请求是否会引起死锁,如果系统中的所有进程存在一个可完成的执行序列P1, P2, P3, ··· , Pn。则成系统处于安全状态,不会出现死锁。
如图,有P0 - P4五个进程,Allocation 表示当前进程已经占用的资源,Need 表示还需要多少资源,Available 表示当前剩余的可用资源。现在需要找出一个可完成的执行序列,让它们避免出现死锁。
先将当前剩余资源分配给P1,P1执行完毕后 Available 变成(5、3、2);接下来将这些剩余资源(5、3、2)分配给P3,P3执行完毕后 Available 变成(7、4、3);同理接下来可以按同样的方法进行,最终会得到一个安全执行序列:P1、P3、P2、P4、P0。然后让这五个进程按照这个执行序列执行,就不会出现死锁。这里有一个经典的银行家算法
就是用来找安全序列的,本文暂不提及。
那如何利用安全序列避免死锁呢?过程是每次当请求出现时,先假装分配某个资源,再调用银行家算法找到一个安全序列,判断它能不能往下安全的执行。但缺点就是,由于银行家算法的时间复杂度为O(mn^2)。(m代表资源剩余数量,n代表进程个数)。并且各个进程并不是只会发出一次资源请求,而是会=在执行过程中不断的申请资源,这就造成了时间复杂度过大,影响效率。 - 死锁检测+恢复的例子
由于使用银行家算法每次资源申请时都会造成时间复杂度太大,所以可以在发现问题时再调用银行家算法处理。比如定时检测或发现资源利用率低时,也就是说当CPU较闲的时候检测。
这样一来虽然可以检测死锁,但想要让进程回滚进行恢复时会带来很多问题。比如选择哪些进程回滚?怎么实现回滚?回滚到哪个位置?那些修改过的文件怎么办?比如有个进程已经将数据写到磁盘文件里了,这时回滚就会带来很多问题。这也就是这种方法的缺点。
综上,所以许多通过操作系统,如PC机上安装的Windows和Linux系统,都采用死锁忽略
的方法,因为死锁忽略的代价最小,这种机器上出现死锁的概率也比其它机器低,死锁可以通过重启来解决,机器重启造成的影响也很小。其它的处理死锁的方法都会让编程变的困难并使资源利用率低。