# 第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/)
