242_死锁的处理策略—预防死锁
# 2.4_2_死锁的处理策略—预防死锁
各位同学大家好,在上一小节的末尾,我们简单提到了死锁处理的几种策略,预防死锁,避免死锁和死锁的检测和解除这个小节,我们会重点介绍预防死锁这种策略。这种策略的主要思想是要想办法破坏死所产生的 4 个必要条件当中的某 1 个
# 破坏互斥条件
经过上一个小节的学习,我们知道只要这 4 个必要条件当中的某 1 个或者几个不成立死锁就肯定不会发生。
首先我们来看一下能不能破坏互斥条件。所谓的互斥条件是指对于必须互斥使用的资源的争抢,才会导致进程之间的死锁。如果说我们能把一种互斥必须互斥使用的资源改造成一种可以同时使用的资源的话,那么这种资源是不是就不会再导致死锁了呢?
其实这件事情是可以实现的,比如说咱们在之后的章节会学到一种叫做 SPOOLing 技术的东西,操作系统采用 SPOOLing 技术之后,就可以把独占必须独占使用的设备,在逻辑上改造成可以共享使用的设备。
我们以用 SPOOLing 技术改造打印机为例,在采用 SPOOLing 技术之前,如果说两个进程都想使用打印机这种资源,那么首先进程 1 它在使用打印机,并且他还没有使用完打印机,这个时候如果进程 2 也想使用打印机的话,他的行为应该被阻止,进程 2 会进入到阻塞态来等待进程 1 释放打印机资源。因为打印机这种资源它是必须互斥使用的,资源同一时刻只能供一个进程使用。
而如果采用了 SPOOLing 技术之后,各个进程对打印机发出的请求会首先被输出进程给接收。同样的进程 2 如果也想输出的话,也会直接被输出进程给接收。那么当他们的这些请求被接收并且被响应了之后,这些进程就可以开始顺利的往下执行别的事情了。之后输出进程会根据各个进程的请求,把它依次放到打印机上打印输出,就像这样子。
所以其实采用了 SPOOLing 技术之后,虽然打印机它是一种必须互斥使用的独占设备,但是进程 1 和进程 2,他们俩即便是同时想要使用打印机的话,那么他们的请求也可以被及时的接收,也就是被输出进程先暂时为他们保管,然后之后再慢慢的输出。
所以从逻辑上看,对于这些进程来说,在他们看来他们所使用的就并不是一个独占设备,而是一个共享设备,在他们看来是这样的。但其实是操作系统采用了 SPOOLing 技术,把互斥使用的资源改造成了一个可以共享使用的资源,因此其实互斥条件是可以破坏的。
但是在很多时候,其实并不是所有的设备都可以被改造成共享的设备,并且甚至为了系统安全,有很多地方还必须保证这种互斥性,所以破坏互斥条件这个思路其实适用的范围并不广,这是它的缺点。
# 破坏不剥夺条件
那么我们能不能换一个思路,是否能破坏不剥夺条件。所谓的不剥夺条件,是指进程所获得的资源,在没有完没有使用完之前,不能由其他进程强行夺走,只能主动释放。那么我们可以用这样的方案来破坏这个条件,比如说当一个进程他在请求一种新的资源,但是新的资源暂时不能分配给他,暂时得不到满足的时候,进程就必须立即释放他手里保持的所有资源,等之后如果还想使用这些资源的话,它就需要重新申请。所以如果采用了这种方案,那么一个进程,他手里的资源哪怕暂时还没有用完,那也有可能会需要主动释放,这就破坏了不可剥夺的条件。
我们还可以用另外一种方式来破坏这个条件,比如说当一个进程需要使用某一种资源,但是这个资源正在被别的进程所占用的时候,那么可以由操作系统协助把他想要的那种资源强行的剥夺。但是这种方式一般来说需要考虑到各个进程的优先级高的进程,可以把别的进程的手里的资源给剥夺,并且归自己使用。比如说咱们之前讲处理机调度的时候,讲过剥夺调度方式,如果说一个系统当中有优先级更高的进程到达的话,那么处理机这种资源会被强行的剥夺,分给优先级更高的进程使用。这种方案也会有一些缺点,
比如说实现起来会比较复杂,
第二,如果说一个进程它所获得的资源需要强行释放或者强行被剥夺的话,那么有可能造成进程之前阶段的工作失效。所以这种方法一般来说只适用于一些比较容易保存和恢复状态的资源,比如说像 CPU,那么在 CPU 资源被剥夺的时候,之前在 CPU 上运行的进程,它的 CPU 寄存器之类的一些中间数据就需要被保存。所以其实从这个角度来看,也可以发现要这种方式实现起来其实还是比较复杂的。
那么第三点,如果我们反复的申请和释放资源的话,那么显然会增加系统开销,从而降低系统的吞吐量,这个比较容易理解。
第四点,如果我们采用第一种方案,那么就意味着只要暂时得不到某一种资源,那么进程手里的资源就需要全部放弃,以后如果还需要这些资源的话,又需要重新再从头申请,因此如果一直发生全部放弃的这种事情的话,那么进程就有可能会一直保持饥饿的状态,它没办法往下推进,这是破坏不可剥夺条件这种策略的一些缺点。
# 破坏请求和保持必要条件
接下来我们再来看一下能不能破坏请求和保持必要条件。所以请求和保持条件是指进程已经保持了至少一个资源,但是又提出了新的资源请求,但是新的资源又被其他的进程所占有,此时进程会被阻塞,但是同时又对自己手里的资源保持不放,所以这是请求和保持条件
我们可以采用静态分配方法来破坏这个条件,就是在一个进程运行之前,一次性申请完他所需要的全部资源。如果说这些资源暂时还不能全部分配给他,进程就暂时不能投入运行,而一旦进程投入运行之后,他所需要的这些资源就会一直归他所有。所以如果采用这样的方式的话,当然就不会存在一边保持着某一种资源不放,一边又请求一个新的资源这样的事情,这就破坏了请求和保持条件。因为如果采用这种方法的话,进程在投入运行之后,肯定就已经拥有了自己所需要的全部资源,已经不会再发出新的请求了。
这种方法它实现起来比较简单,但是也有明显的缺点。有的资源它可能只需要使用很短的时间,但是由于所有的资源在刚开始就需要被分配给进程,并且在它整个运行期间这些资源都会被它被这个进程所占有。所以对于那些使用的频率不高的资源,其实是会有很多时间是闲置的,这就造成了系统资源的严重浪费,系统资源的利用率会降低。
另外这种策略也有可能会导致进程的饥饿现象。比如说一个系统当中有资源 1 和资源 2 这两种资源,然后有 a 类、b 类、c 类这三种进程,a 类进程只需要使用资源 1 就可以开始投入运行,而 b 类进程只需要使用资源 2 就可以开始运行,而 c 类资源它需要同时拥有资源 1 和资源 2 才可以投入运行。
如果说系统当中有源源不断的 a 类和 b 类进程到达的话,那么资源 1 一旦被释放,它又会被立即分配给下一个 a 类进程。资源 2 一旦被释放,他也会被立即分配给下一个 b 类进程,而除非资源 1 和资源 2 都没有进程使用的时候,都空闲的时候,他们才有可能同时被分配给一个 c 类进程,这样的话 c 类进程才可以开始投入运行,所以很显然这种方式是有可能会导致 c 类进程饥饿的。
# 破坏循环等待条件
那么接下来我们再来看是否能破坏循环等待条件。所谓循环等待条件就是指一种存在一种进程资源的循环等待链,链中的每一个进程,已获得的资源,同时再被下一个进程所请求。
我们可以采用顺序资源分配法来破坏这个条件,可以首先给系统当中的各种资源进行一个编号,并且规定各个进程必须按照编号递增的顺序来依次请求这些资源,而对于编号相同的那些同类资源必须一次申请完。我们可以这么来考虑,按照这种规则,一个进程只有占有了小编号的资源的时候,他才有资格去申请更大编号的资源,并且已经持有了大编号资源的进程,是不可能逆向的回来申请小编号资源的。
所以如果发生进程的相互等待的话,那么只有可能是拥有小编号资源的进程,在等待拥有大编号资源的进程,而不可能是拥有大编号资源的进程,反向回来等待拥有小编号资源的进程。因此这就不可能发生循环等待链,所以这就是这种方案能可以破坏循环等待条件的原因,
那么我们还可以从另外一个角度来分析,假设一个系统当中有 10 个资源编号分别为 1~10 号,那么 p1 进程占用了 1 号和 3 号,p2 占用了 2 号和 4 号,p3 占用了 5 号和 7 号,所以不管在什么时刻,系统当中肯定会有一个进程,它所拥有的资源编号是最大的。比如说像这个地方的 p3 进程,那么也就意味着大于 7 号的那些资源,八九十号资源此时肯定是空闲的,没有被任何进程所占用。
所以如果说 p3 进程继续往下执行的话,那么他所申请的资源肯定只可能是大于 7 号的那些资源,也就是 8 号 9 号 10 号,而这些资源肯定可以畅通无阻的全部分配给 p3 进程,因此至少 p3 进程是可以顺利的获得所有他所需要的资源,并且顺利的执行结束的。所以从这个角度来分析,也不可能出现所有的进程都阻塞的这种死锁现象
不过和之前的那几种方案一样,破坏循环等待条件其实也存在一些缺点,
因为系统当中的各种资源都需要给他一个编号,如果说系统当中想要新增一种设备,那么就有可能会需要重新对所有的这些设备都进行编号,显然这是很不方便的。
第二,如果说一个进程,它实际使用资源的顺序和这些编号递增的顺序不一致的话,就有可能会导致资源的浪费。比如说 p3 进程需要使用 5 号资源打印机,也需要使用 7 号资源扫描仪,但是实际的使用过程中,p3 进程它是需要先使用扫描仪再使用打印机的,但是由于编号递增的这种要求,p3 进程又必须先申请占有他暂时用不到的资源,也就是打印机之后打印机会空闲很长一段时间,一直到扫描仪被使用完了,才会回头再使用打印机这种资源,所以这就造成了打印机资源的长时间空闲,因此就导致了系统资源的浪费。
第三,因为必须按照这些编号递增的顺序来申请资源,所以用户编程是很麻烦的。还是用 p3 进程为例,比如说在一个系统当中,打印机的编号是 5 号,扫描仪的编号是 7 号,一个用户程序如果既需要使用打印机,又是需要使用打扫描仪的话,用户编程的时候就需要先编写申请使用打印机的代码,因为 5 号因为打印机的编号是更小的,之后再写申请使用扫描仪的代码,而如果换一个系统,另一个系统对扫描仪和打印机的编号刚好是相反的,扫描仪的编号更小,打印机的编号更大,那么用户的程序就需要为此发生改变,它需要把申请扫描仪资源的代码把它放到申请打印机代码之前,所以很显然这种方式会造成用户编程的极大不便。
# 小结
可以看到这个小节介绍的预防死锁的这些策略或多或少都存在一些缺陷,这个小节的内容比较容易结合死锁产生的 4 个必要条件,在选择题当中进行考察,同学们主要是以理解为主,不需要死记硬背,当然也需要稍微的记忆一下,大概可以用什么样的方法来破坏这些必要条件。
另外之前咱们在讲哲学家进餐问题的时候,讲到了三种处理死锁的方法,学习完小节的内容之后,大家可以再回头去分析一下,三种方法分别是破坏了哪一个条件?复习考研的过程中,其实这样的事情是很关键的,需要在学习后面的内容之后,在和前面的内容进行联系,把所有的这些知识都织成一个网状,而不是一个独立的点状的知识。