死锁的概念
2.3.5 的哲学家进餐已经演示过一次:5 人同时拿起左边的筷子,每人持一根、等一根,全体卡死。这一节把那个现象一般化。
本节的核心是四个必要条件——2.4 后面三节(预防、避免、检测)全部围绕它们展开。
机制
什么是死锁
死锁:多个进程各自占有一部分资源,同时又在等待其他进程占有的资源,导致所有进程都无法向前推进的僵局。
三个要素缺一不可:多个进程、互相持有对方所需的资源、都进入阻塞且无法自行解除。
产生死锁的原因
疑问点:教材列举的死锁成因应如何把握,是否有一条稳妥的表述
教材列出”系统资源的竞争""进程推进顺序非法""信号量使用不当”等成因, 这些条目彼此并列却层次不一,需要一条稳妥可用的口径。
最稳妥的口径是:死锁的成因只有一条——“进程各自持有一部分资源,同时又在等待别人持有的资源,形成了环”。教材列的那几项,是这个环得以形成的不同途径。
按这条口径重读教材的三项,层次就清楚了:
① 系统资源的竞争——这是最直接的途径。资源数量不足以同时满足多个进程,于是各自抢到一部分后互相僵持。
这一项里有一句必须单独记住的话:
只有对不可剥夺资源的竞争才可能产生死锁;对可剥夺资源(CPU、主存)的竞争不会引起死锁。
理由很直白:可剥夺资源随时能被操作系统收回再分配,环随时可以被打破。CPU 被时间片轮转不断收回,主存可以被换出,它们永远不会成为死锁的成因。这一点是选择题的固定考点。
② 进程推进顺序非法——同样一组请求,换一个交错顺序就不死锁。
比如 P₁ 持有 R₁ 要 R₂,P₂ 持有 R₂ 要 R₁ 时死锁;但若 P₁ 先完整地跑完再让 P₂ 跑,就完全没事。死锁不是必然发生的,它取决于推进的时序——这正好呼应了 2.1.1 的”并发进程失去可再现性”。
③ 信号量使用不当——本质上是②的一个特例。
2.3.4 讲的”P 操作顺序写反导致抱着锁睡觉”就是典型例子。此外进程间互相等待对方发来的消息也属此类——注意这种情况下它们并非竞争同一个资源,而是互相等待对方产出的东西,但环的结构完全一样。
三项的关系:①说的是”为什么会争”,②说的是”为什么争会出事”,③是②在同步机制上的具体表现。它们不是三个并列的原因,而是同一件事的三个观察角度。
四个必要条件
死锁发生时,以下四个条件必然同时成立:
① 互斥条件——资源在一段时间内只能由一个进程占有。
② 不可剥夺条件——进程已获得的资源在使用完之前不能被其他进程强行夺走,只能由自己主动释放。
③ 请求并保持条件——进程已经保持了至少一个资源,又提出新的请求;新资源被占有而请求被阻塞时,它对自己已有的资源保持不放。
④ 循环等待条件——存在一个进程-资源的环形链,链中每个进程都在等待下一个进程所占有的资源。
这四条是必要条件:死锁发生 ⟹ 四条全部成立。因此破坏其中任意一条,就能保证不发生死锁——这正是 2.4.2 死锁预防的全部思路。
死锁的处理策略
四种策略,按”什么时候管”排列:
从上到下,对资源分配的限制越来越宽松,资源利用率越来越高,但处理代价也越来越大。
边界
不可剥夺条件 vs 请求并保持条件
疑问点:如何区分不可剥夺条件与请求并保持条件
两个条件都涉及”资源留在持有者手中”,其区别何在?
判据是看问题的视角:不可剥夺是”别人抢不走”,请求并保持是”自己不放手”。
| 不可剥夺条件 | 请求并保持条件 | |
|---|---|---|
| 视角 | 外部——其他进程和 OS | 自身——持有者本人 |
| 说的是 | 别人无权强行拿走 | 自己主动攥着不放 |
| 场景 | 进程正在正常使用资源时 | 进程因新请求而阻塞时 |
| 破坏方法 | 申请不到新资源时,释放(或被剥夺)已有的全部资源 | 一次性申请全部所需资源(静态分配) |
关键差别在于它们描述的是不同时刻:
- 不可剥夺描述的是平时——只要我还在用,谁都拿不走。
- 请求并保持描述的是阻塞时——我已经因为要不到新资源而卡住了,明明自己也没在用手里的东西,却依然占着不放。
这个时刻上的区别,直接决定了两者的破坏方法完全不同:破坏”不可剥夺”是让资源能被拿走,破坏”请求并保持”是让进程不产生”边持有边等待”的状态。
死锁 vs 饥饿 vs 死循环
三者都表现为”进程推进不了”,但机制完全不同,是高频辨析点:
| 死锁 | 饥饿 | 死循环 | |
|---|---|---|---|
| 涉及进程数 | 至少 2 个 | 1 个即可 | 1 个 |
| 进程状态 | 阻塞态 | 通常阻塞(也可能忙等) | 运行态 |
| 是否占用 CPU | 否 | 通常否 | 是 |
| 成因 | 循环等待,资源分配不当 | 调度策略不公平 | 程序自身的逻辑错误 |
| 能否自行解除 | 绝无可能 | 理论上可能(概率极低) | 否 |
| 归属 | 操作系统问题 | 操作系统问题 | 程序员的 bug |
最容易混的是死锁与饥饿:死锁的进程在等待一个”永远不会被释放”的资源;饥饿的进程等待的资源会被释放,只是每次都轮不到它。
而死循环是唯一一个不属于操作系统问题的——进程一直在正常运行、一直在占用 CPU,操作系统看不出任何异常。
循环等待是必要条件,但不是充分条件
这是本节最容易出错的一点。
“发生死锁 ⟹ 存在循环等待”成立,但反过来不成立。
反例出现在每类资源有多个实例时。设有 3 台打印机,P₁、P₂ 各持 1 台并互相等待对方的,形成了环;但此时还有第 3 台空闲着,或者存在 P₃ 用完后会释放一台——环虽然画得出来,但资源足以让某个进程推进下去,死锁并未发生。
因此准确的表述是:
- 每类资源只有一个实例时:循环等待 ⟺ 死锁(环即死锁)
- 每类资源有多个实例时:循环等待是死锁的必要不充分条件(有环未必死锁)
这条边界直接决定了 2.4.4 为什么不能只靠”图里有没有环”来判断死锁,而必须用资源分配图化简。
只有不可剥夺资源才会导致死锁
CPU 和主存是可剥夺资源,对它们的竞争不会产生死锁。
- CPU:时间片一到就被收回,永远不可能被某个进程永久占有。
- 主存:可以通过对换把进程整个换出到外存,从而收回其内存。
只有打印机、磁带机这类用一半不能中途拿走的资源,才具备构成死锁的资格。
对照速查
| 四个必要条件 | 含义 | 破坏方法 |
|---|---|---|
| ① 互斥 | 资源一次只能给一个进程 | 改造成可共享(通常无法实现) |
| ② 不可剥夺 | 别人抢不走 | 申请不到就释放已有全部资源 |
| ③ 请求并保持 | 自己不放手 | 一次性申请全部资源 |
| ④ 循环等待 | 存在环形等待链 | 顺序资源分配法(按编号递增申请) |
| 处理策略 | 时机 | 限制程度 | 资源利用率 |
|---|---|---|---|
| 预防 | 事前 | 最严 | 最低 |
| 避免 | 事中 | 中 | 中 |
| 检测与解除 | 事后 | 最松 | 最高 |
考点
- 四个必要条件的名称与含义(一切的基础)
- 不可剥夺 = 别人抢不走;请求并保持 = 自己不放手
- 只有不可剥夺资源的竞争才可能死锁;CPU、主存不会
- 循环等待是必要不充分条件(多实例资源时有环未必死锁)
- 死锁 / 饥饿 / 死循环的三方辨析
- 四种处理策略的时机与宽松程度排序
链接
- 🏠 返回总览:操作系统第 2 章:进程与线程总览
- ⬅️ 上一节:2.3.6 管程
- ➡️ 下一节:2.4.2 死锁预防
- 🔗 经典模型见 哲学家进餐问题
- 📖 名词库:第 2 章名词库