Files

377 lines
8.8 KiB
Markdown
Raw Permalink Normal View History

2026-06-13 23:46:22 +08:00
# 第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/)