本章介绍并发处理中需要解决的两个问题:死锁和饥饿。本章首先讨论死锁的基本原理和饥饿的相关问题;接着分析处理死锁的三种常用方法:预防、检测和避免;然后考虑用于说明同步和死锁的一个经典问题;哲学家就餐问题。
作为对全局知识的把控,这里给出学习目标,作为参考:
- 列举并解释死锁产生的条件
- 定义死锁预防,针对死锁产生的条件给出死锁预防的策略
- 理解死锁预防与死锁避免的区别
- 掌握死锁避免的两种方法
- 理解死锁检测与死锁预防、死锁检测与死锁避免的本质区别
- 掌握设计综合死锁解决策略的方法
- 分析哲学家就餐问题
6.1 死锁原理
死锁定义为一组相互竞争系统资源或进行通信的进程间的“永久”阻塞。当一组进程中的每个进程都在等待某个事件(典型情况下是等待释放所请求的资源),而仅有这组进程中被阻塞的其他进程才可触发该事件时,就称这组进程发生了死锁。因为没有事件能够被触发,故死锁是永久性的。与并发进程管理中的其他问题不同,死锁问题并无有效的通用解决方案。
6.1.1 可重用资源
资源通常分为两类:可重用资源和可消耗资源。可重用资源是指一次仅供一个进程安全使用且不因使用而耗尽的资源。进程得到资源单元并使用后,会释放这些单元供其他进程再次使用。可重用资源的例子包括处理器、I/O通道、内存和外存、设备,以及诸如文件、数据库和信号量之类的数据结构。
下面给出一个涉及可重用资源死锁的例子。考虑相互竞争的两个进程都要独占访问磁盘文件D和磁带设备T的情形。程序执行如图6.4所示的操作。每个进程都占用一个资源并请求另一个资源时,就会发生死锁;例如,多道程序设计系统交替地执行两个进程时会发生死锁,如下所示:
这看起来更像程序设计错误而非操作系统设计人员的问题。由于并发程序设计非常具有挑战性,因此这类死锁的确会发生,而发生的原因通常隐藏在复杂的程序逻辑中,因此检测非常困难。处理这类死锁的一个策略是,给系统设计施加关于资源请求顺序的约束。
可重用资源死锁的另一个例子是内存请求。假设可用的分配空间为200KB,且发生的请求序列如下所示:
两个进程都行进到它们的第二个请求时,就会发生死锁。因为对方都不释放内存,那么另外一个进程就无法申请到新的内存,如果不申请到新的内存,就不会继续执行以释放内存,这就是产生死锁的根本原因所在。 若事先并不知道请求的存储空间总量,则很难通过系统设计约束来处理这类死锁。解决这类特殊问题的最好办法是,使用虚存有效地消除这种可能性。
在可重用资源上发生死锁是非常典型的现象。有的人可能对死锁的影响也只停留于于此,但是还有一种可消耗资源上的死锁,也很常见。
6.1.2 可消耗资源
可消耗资源是指可被创建(生产)和销毁(消耗)的资源。某种类型可消耗资源的数量通常没有限制,无阻塞生产进程可以创建任意数量的这类资源。消费进程得到一个资源时,该资源就不再存在。可消耗资源的例子有中断、信号、消息和I/O缓冲区中的信息。
作为一个涉及可消耗资源死锁的例子,考虑下面的进程对,其中的每个进程都试图从另一个进程接收消息,然后再给那个进程发送一条消息: