vault backup: 2026-06-13 23:46:22
This commit is contained in:
376
操作系统/12_死锁/12_死锁.md
Normal file
376
操作系统/12_死锁/12_死锁.md
Normal file
@@ -0,0 +1,376 @@
|
||||
# 第12讲:死锁
|
||||
|
||||
> 🎯 **本节目标**:理解死锁的概念,掌握死锁的预防、避免和检测方法
|
||||
|
||||
## 📋 前置知识
|
||||
- [[07_多线程编程]] — 互斥锁和信号量
|
||||
- [[11_处理机调度]] — 调度的基本概念
|
||||
|
||||
---
|
||||
|
||||
## 🤔 为什么需要这个?
|
||||
|
||||
想象这样一个场景:
|
||||
- 线程 A 持有锁 1,等待锁 2
|
||||
- 线程 B 持有锁 2,等待锁 1
|
||||
- 两个线程互相等待,永远无法继续
|
||||
|
||||
这就是**死锁**——多个进程互相等待对方释放资源,导致所有进程都无法继续执行。
|
||||
|
||||
**生活比喻**:
|
||||
- **死锁** = 两个人在狭窄的走廊相遇,谁都不肯让路,结果谁也过不去
|
||||
|
||||
---
|
||||
|
||||
## 📖 核心概念
|
||||
|
||||
### 1. 死锁的四个必要条件
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[死锁四个条件] --> B[互斥条件]
|
||||
A --> C[持有并等待]
|
||||
A --> D[不可抢占]
|
||||
A --> E[循环等待]
|
||||
|
||||
B --> B1[资源一次只能被一个进程使用]
|
||||
C --> C1[进程持有资源的同时请求新资源]
|
||||
D --> D1[资源不能被强制剥夺]
|
||||
E --> E1[存在进程的循环等待链]
|
||||
|
||||
style A fill:#ffcdd2
|
||||
```
|
||||
|
||||
**必须同时满足这四个条件才会发生死锁**。
|
||||
|
||||
### 2. 资源分配图
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph 进程
|
||||
P1[P1]
|
||||
P2[P2]
|
||||
end
|
||||
subgraph 资源
|
||||
R1[R1]
|
||||
R2[R2]
|
||||
end
|
||||
|
||||
R1 -->|分配| P1
|
||||
R2 -->|分配| P2
|
||||
P1 -->|请求| R2
|
||||
P2 -->|请求| R1
|
||||
|
||||
style P1 fill:#e1f5fe
|
||||
style P2 fill:#e1f5fe
|
||||
style R1 fill:#e8f5e9
|
||||
style R2 fill:#e8f5e9
|
||||
```
|
||||
|
||||
**图例**:
|
||||
- 方框表示资源,圆圈表示进程
|
||||
- 资源→进程:已分配
|
||||
- 进程→资源:请求中
|
||||
|
||||
### 3. 死锁的处理策略
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[死锁处理策略] --> B[死锁预防]
|
||||
A --> C[死锁避免]
|
||||
A --> D[死锁检测]
|
||||
A --> E[死锁恢复]
|
||||
|
||||
B --> B1[破坏四个条件之一]
|
||||
C --> C1[银行家算法]
|
||||
D --> D1[资源分配图]
|
||||
E --> E1[终止进程]
|
||||
|
||||
style B fill:#e8f5e9
|
||||
style C fill:#e1f5fe
|
||||
style D fill:#fff3e0
|
||||
style E fill:#ffcdd2
|
||||
```
|
||||
|
||||
### 4. 死锁预防
|
||||
|
||||
破坏死锁的四个必要条件之一:
|
||||
|
||||
| 条件 | 破坏方法 | 代价 |
|
||||
|------|----------|------|
|
||||
| 互斥 | 使用可共享资源 | 不总是可行 |
|
||||
| 持有并等待 | 一次性申请所有资源 | 资源浪费 |
|
||||
| 不可抢占 | 允许抢占资源 | 实现复杂 |
|
||||
| 循环等待 | 按顺序申请资源 | 限制灵活性 |
|
||||
|
||||
**资源有序分配法**:
|
||||
```c
|
||||
// 规定所有进程必须按编号顺序申请资源
|
||||
// 例如:先申请锁1,再申请锁2
|
||||
|
||||
pthread_mutex_lock(&mutex1); // 正确
|
||||
pthread_mutex_lock(&mutex2);
|
||||
|
||||
// 而不是
|
||||
pthread_mutex_lock(&mutex2); // 可能导致死锁
|
||||
pthread_mutex_lock(&mutex1);
|
||||
```
|
||||
|
||||
### 5. 银行家算法
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[银行家算法] --> B[检查请求是否安全]
|
||||
B --> C{安全?}
|
||||
C -->|是| D[分配资源]
|
||||
C -->|否| E[等待]
|
||||
D --> F[更新数据结构]
|
||||
E --> G[阻塞进程]
|
||||
|
||||
style C fill:#fff3e0
|
||||
style D fill:#e8f5e9
|
||||
style E fill:#ffcdd2
|
||||
```
|
||||
|
||||
**安全状态**:存在一个安全序列,使得所有进程都能顺利完成
|
||||
|
||||
**数据结构**:
|
||||
- `Available[]`:可用资源向量
|
||||
- `Max[][]`:最大需求矩阵
|
||||
- `Allocation[][]`:已分配矩阵
|
||||
- `Need[][]`:还需要的资源矩阵
|
||||
|
||||
**算法步骤**:
|
||||
1. 检查请求是否超过需要
|
||||
2. 检查请求是否超过可用资源
|
||||
3. 尝试分配,检查是否安全
|
||||
4. 如果安全,正式分配;否则等待
|
||||
|
||||
### 6. 死锁检测
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[构建资源分配图] --> B[寻找环路]
|
||||
B --> C{有环路?}
|
||||
C -->|是| D[可能存在死锁]
|
||||
C -->|否| E[无死锁]
|
||||
D --> F[进一步分析]
|
||||
|
||||
style C fill:#fff3e0
|
||||
style D fill:#ffcdd2
|
||||
style E fill:#e8f5e9
|
||||
```
|
||||
|
||||
**检测算法**:
|
||||
1. 构建资源分配图
|
||||
2. 使用深度优先搜索寻找环路
|
||||
3. 如果存在环路,可能存在死锁
|
||||
|
||||
### 7. 死锁恢复
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[检测到死锁] --> B[选择终止进程]
|
||||
B --> C[回滚操作]
|
||||
C --> D[释放资源]
|
||||
D --> E[唤醒等待进程]
|
||||
|
||||
style A fill:#ffcdd2
|
||||
style E fill:#e8f5e9
|
||||
```
|
||||
|
||||
**恢复方法**:
|
||||
- **终止所有死锁进程**:简单但代价大
|
||||
- **逐个终止进程**:直到死锁解除
|
||||
- **资源抢占**:强制剥夺资源
|
||||
|
||||
---
|
||||
|
||||
## 💻 动手实践
|
||||
|
||||
### 示例1:死锁演示
|
||||
|
||||
```c
|
||||
// deadlock_demo.c - 死锁演示
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
|
||||
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
void *thread1(void *arg) {
|
||||
pthread_mutex_lock(&mutex1); // 获取锁1
|
||||
printf("Thread 1: 持有锁1,等待锁2...\n");
|
||||
sleep(1); // 等待,让线程2获取锁2
|
||||
pthread_mutex_lock(&mutex2); // 等待锁2(死锁!)
|
||||
printf("Thread 1: 获取到锁2\n");
|
||||
|
||||
pthread_mutex_unlock(&mutex2);
|
||||
pthread_mutex_unlock(&mutex1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *thread2(void *arg) {
|
||||
pthread_mutex_lock(&mutex2); // 获取锁2
|
||||
printf("Thread 2: 持有锁2,等待锁1...\n");
|
||||
sleep(1); // 等待,让线程1获取锁1
|
||||
pthread_mutex_lock(&mutex1); // 等待锁1(死锁!)
|
||||
printf("Thread 2: 获取到锁1\n");
|
||||
|
||||
pthread_mutex_unlock(&mutex1);
|
||||
pthread_mutex_unlock(&mutex2);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main() {
|
||||
pthread_t tid1, tid2;
|
||||
|
||||
pthread_create(&tid1, NULL, thread1, NULL);
|
||||
pthread_create(&tid2, NULL, thread2, NULL);
|
||||
|
||||
pthread_join(tid1, NULL);
|
||||
pthread_join(tid2, NULL);
|
||||
|
||||
printf("程序正常结束(如果这里能打印说明没有死锁)\n");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**编译运行**:
|
||||
```bash
|
||||
gcc -o deadlock deadlock_demo.c -lpthread
|
||||
./deadlock
|
||||
```
|
||||
|
||||
**预期结果**:程序会卡住,无法正常结束(死锁)
|
||||
|
||||
### 示例2:避免死锁
|
||||
|
||||
```c
|
||||
// no_deadlock.c - 避免死锁(按顺序获取锁)
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
|
||||
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
void *thread1(void *arg) {
|
||||
pthread_mutex_lock(&mutex1); // 先获取锁1
|
||||
printf("Thread 1: 持有锁1\n");
|
||||
sleep(1);
|
||||
pthread_mutex_lock(&mutex2); // 再获取锁2
|
||||
printf("Thread 1: 持有锁1和锁2\n");
|
||||
|
||||
pthread_mutex_unlock(&mutex2);
|
||||
pthread_mutex_unlock(&mutex1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *thread2(void *arg) {
|
||||
pthread_mutex_lock(&mutex1); // 也先获取锁1(顺序一致)
|
||||
printf("Thread 2: 持有锁1\n");
|
||||
sleep(1);
|
||||
pthread_mutex_lock(&mutex2); // 再获取锁2
|
||||
printf("Thread 2: 持有锁1和锁2\n");
|
||||
|
||||
pthread_mutex_unlock(&mutex2);
|
||||
pthread_mutex_unlock(&mutex1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main() {
|
||||
pthread_t tid1, tid2;
|
||||
|
||||
pthread_create(&tid1, NULL, thread1, NULL);
|
||||
pthread_create(&tid2, NULL, thread2, NULL);
|
||||
|
||||
pthread_join(tid1, NULL);
|
||||
pthread_join(tid2, NULL);
|
||||
|
||||
printf("程序正常结束\n");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 示例3:银行家算法模拟
|
||||
|
||||
```python
|
||||
# banker.py - 银行家算法模拟
|
||||
def is_safe(available, max_need, allocation):
|
||||
"""检查系统是否处于安全状态"""
|
||||
n = len(allocation) # 进程数
|
||||
m = len(available) # 资源类型数
|
||||
|
||||
# 计算 Need 矩阵
|
||||
need = [[max_need[i][j] - allocation[i][j] for j in range(m)] for i in range(n)]
|
||||
|
||||
# 初始化工作向量和完成标志
|
||||
work = available.copy()
|
||||
finish = [False] * n
|
||||
safe_seq = []
|
||||
|
||||
while len(safe_seq) < n:
|
||||
found = False
|
||||
for i in range(n):
|
||||
if not finish[i]:
|
||||
# 检查是否可以分配
|
||||
if all(need[i][j] <= work[j] for j in range(m)):
|
||||
# 模拟分配
|
||||
for j in range(m):
|
||||
work[j] += allocation[i][j]
|
||||
finish[i] = True
|
||||
safe_seq.append(i)
|
||||
found = True
|
||||
|
||||
if not found:
|
||||
return False, [] # 不安全
|
||||
|
||||
return True, safe_seq
|
||||
|
||||
# 测试数据
|
||||
available = [3, 3, 2]
|
||||
max_need = [
|
||||
[7, 5, 3],
|
||||
[3, 2, 2],
|
||||
[9, 0, 2],
|
||||
[2, 2, 2],
|
||||
[4, 3, 3]
|
||||
]
|
||||
allocation = [
|
||||
[0, 1, 0],
|
||||
[2, 0, 0],
|
||||
[3, 0, 2],
|
||||
[2, 1, 1],
|
||||
[0, 0, 2]
|
||||
]
|
||||
|
||||
safe, seq = is_safe(available, max_need, allocation)
|
||||
if safe:
|
||||
print(f"系统安全,安全序列: {seq}")
|
||||
else:
|
||||
print("系统不安全")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 知识关联
|
||||
- 互斥锁在 [[07_多线程编程]] 中有详细讲解
|
||||
- 资源分配在 [[05_磁盘空间管理]] 中有类似概念
|
||||
- 银行家算法在 [[14_分页存储管理]] 中的页面置换有类似思想
|
||||
|
||||
---
|
||||
|
||||
## 📝 思考题
|
||||
|
||||
1. **为什么需要同时满足四个条件?** 如果只有三个条件满足会怎样?
|
||||
2. **银行家算法的局限性**:为什么它在实际系统中很少使用?
|
||||
3. **鸵鸟策略**:为什么有些系统选择忽略死锁问题?
|
||||
|
||||
---
|
||||
|
||||
## 📚 扩展阅读
|
||||
- 《操作系统概念》第7章:死锁
|
||||
- 《现代操作系统》第6章:死锁
|
||||
- [死锁检测算法](https://www.geeksforgeeks.org/deadlock-detection-algorithm/)
|
||||
Reference in New Issue
Block a user