揭开死锁谜团:让你的系统永不停转的终极指南🪄

298 阅读10分钟

操作系统中的死锁问题

死锁的概念

死锁(Deadlock)是指在计算机系统中,多个进程因竞争资源而陷入相互等待状态,导致所有进程都无法继续执行的一种现象。就像几个小朋友同时想要玩具,但每个人都握着别人的玩具不放手,结果大家都玩不了。😂

死锁的四个必要条件

  1. 互斥条件(Mutual Exclusion)✋:资源是独占的,一个时间点只能有一个进程使用。
  2. 占有并等待条件(Hold and Wait)⌛:进程已经持有至少一个资源,并且还在等待其他资源。
  3. 不剥夺条件(No Preemption)🚫:资源不能被强行剥夺,只能由持有的进程自己释放。
  4. 环路等待条件(Circular Wait)🔄:存在一个进程循环链,每个进程都在等待下一个进程所持有的资源。

死锁预防(破坏必要条件)

预防死锁的方法是通过破坏上述必要条件之一,来避免系统进入死锁状态。

  1. 破坏互斥条件:让资源可以共享。比如让小朋友们一起玩同一个玩具。不过这在很多情况下难以实现,因为有些资源必须独占使用。

    • 优点:简单直接。
    • 缺点:不适用于必须独占使用的资源。
    • 实现方法:让资源可以共享,不再要求资源只能由一个进程独占使用。这可以通过设计共享资源池、引入可重入锁等方式实现。
    • 可能的算法:信号量机制、共享内存、互斥对象池等。
  2. 破坏占有并等待条件:要求进程在开始时一次性请求所有需要的资源,或者在请求新的资源前释放掉已经占有的资源。就像小朋友们必须一次性拿到所有想要的玩具,否则就不能玩。

    • 优点:有效防止资源等待。
    • 缺点:可能会造成资源浪费,降低系统并发性。
    • 实现方法:要求进程在开始时一次性请求所有需要的资源,或者在请求新的资源前释放掉已经占有的资源。可以引入预留资源、资源分配顺序规定等方式。
    • 可能的算法:预分配资源、资源预留策略、资源分配顺序规定等。
  3. 破坏不剥夺条件:允许资源被强行剥夺。当一个进程请求资源时,如果资源不够,可以从其他占有资源的进程中剥夺。就像小朋友可以把别人的玩具拿过来玩。

    • 优点:有效防止资源长时间占用。
    • 缺点:可能导致进程频繁切换和状态保存,增加系统开销。
    • 实现方法:允许资源被强行剥夺。当一个进程请求资源时,如果资源不够,可以从其他占有资源的进程中剥夺。可以实现资源抢占机制、优先级调整等方式。
    • 可能的算法:资源抢占策略、优先级调度算法等。
  4. 破坏环路等待条件:对资源进行排序,进程必须按序请求资源,保证不会形成环路等待。就像小朋友们按顺序拿玩具,避免形成循环等待。

    • 优点:简单有效。
    • 缺点:需要对资源进行严格排序,实际操作中可能不便。
    • 实现方法:对资源进行排序,进程必须按序请求资源,保证不会形成环路等待。可以利用图论中的拓扑排序算法对资源依赖关系进行排序,从而避免形成环路等待。
    • 可能的算法:拓扑排序、资源分级分配、资源请求顺序规定等。

死锁避免(动态检查)

避免死锁的方法是通过动态检查系统状态,确保系统始终处于一个安全状态。最经典的算法是银行家算法

银行家算法(Banker's Algorithm)- 找到能够运行的一个安全序列 - 赌徒放贷思想

核心概念

  • 可用资源向量(Available):系统当前可以分配的资源数。
  • 最大需求矩阵(Max):每个进程对资源的最大需求。
  • 分配矩阵(Allocation):每个进程当前已经分配的资源数。
  • 需求矩阵(Need):每个进程还需要的资源数。

核心思想

银行家算法的核心思想是模拟资源分配,确保系统始终处于一个安全状态。每次资源请求前,系统会检查模拟分配后的状态是否安全。如果分配后系统处于安全状态,则允许分配,否则拒绝请求。

安全状态和安全序列

安全状态:系统中存在一个进程序列,使得每个进程在最大需求得到满足前,系统都有足够的资源满足其他进程的需求。 安全序列:一个进程的排列顺序,使得每个进程可以依次获得资源并完成执行。

如何找到安全序列

  1. 初始化工作向量 Work 为当前可用资源向量 Available
  2. 初始化完成向量 FinishFalse
  3. 查找一个 FinishFalse 的进程,且其需求小于等于 Work
  4. 如果找到该进程,则将 Work 加上该进程的分配资源,并将 Finish 置为 True
  5. 重复步骤3,直到找到一个安全序列或者无法找到这样的进程。

银行家算法的变化过程

当一个进程请求资源时,系统会模拟分配资源,并检查分配后的系统状态。如果分配后系统仍处于安全状态,则实际分配资源;否则拒绝请求。

代码示例

# 检查系统是否处于安全状态
def is_safe(avail, alloc, max_need):
    n = len(alloc)  # 进程数
    m = len(avail)  # 资源种类数
    work = avail[:]  # 可用资源向量
    finish = [False] * n  # 记录进程是否能完成
    
    # 临时安全顺序
    safe_seq = []
    
    while len(safe_seq) < n:
        allocated = False
        for i in range(n):
            if not finish[i]:
                if all(max_need[i][j] - alloc[i][j] <= work[j] for j in range(m)):
                    for j in range(m):
                        work[j] += alloc[i][j]
                    finish[i] = True
                    safe_seq.append(i)
                    allocated = True
                    break
        if not allocated:
            return False, []
    
    return True, safe_seq

# 资源分配请求
def request_resources(avail, alloc, max_need, req, pid):
    n = len(alloc)
    m = len(avail)
    
    # 检查请求是否大于进程的最大需求
    if any(req[j] > max_need[pid][j] - alloc[pid][j] for j in range(m)):
        return False, "Request exceeds process's maximum claim."
    
    # 检查请求是否大于可用资源
    if any(req[j] > avail[j] for j in range(m)):
        return False, "Request exceeds available resources."
    
    # 尝试分配资源
    for j in range(m):
        avail[j] -= req[j]
        alloc[pid][j] += req[j]
    
    # 检查是否处于安全状态
    is_safe_state, safe_seq = is_safe(avail, alloc, max_need)
    
    if is_safe_state:
        return True, "Resources allocated successfully."
    else:
        # 回滚分配
        for j in range(m):
            avail[j] += req[j]
            alloc[pid][j] -= req[j]
        return False, "Request leads to unsafe state."

好的,我们来扩展银行家算法的示例,展示一种选择了某个进程后无法找到安全序列的情况,并解释如何回到安全点继续寻找其他选择。

假设有4个进程(P1, P2, P3, P4)和3类资源(R1, R2, R3),资源数量分别为(10, 5, 7)。初始分配如下:

初始状态

需求矩阵 (Claim matrix, C)

进程R1R2R3
P1322
P2613
P3314
P4422

分配矩阵 (Allocation matrix, A)

进程R1R2R3
P1100
P2511
P3211
P4002

需求矩阵与分配矩阵的差 (C - A)

进程R1R2R3
P1222
P2102
P3103
P4420

资源向量 (Resource vector, R)

资源总数
R110
R25
R37

可用向量 (Available vector, V)

根据初始分配和资源总数,可以计算可用资源数:

Available = R - sum(Allocated)

可用向量计算如下:

R1: 10 - (1 + 5 + 2 + 0) = 10 - 8 = 2
R2: 5 - (0 + 1 + 1 + 0) = 5 - 2 = 3
R3: 7 - (0 + 1 + 1 + 2) = 7 - 4 = 3

所以初始可用向量为:

资源可用数
R12
R23
R33

P2运行到完成

P2运行完成并释放资源后的更新:

更新后的分配矩阵 (Allocation matrix, A)

进程R1R2R3
P1100
P2000<- P2释放资源
P3211
P4002

更新后的可用向量 (Available vector, V)

R1: 2 + 5 = 7
R2: 3 + 1 = 4
R3: 3 + 1 = 4

所以新的可用向量为:

资源可用数
R17
R24
R34

尝试选择进程

尝试选择P1

进程需求 (C - A)可用 (V)
P1(2, 2, 2)(7, 4, 4)

P1的需求(2, 2, 2) ≤ 可用(7, 4, 4),分配资源给P1,P1完成后释放资源:

更新后的可用向量:Available = (7, 4, 4) + (1, 0, 0) = (8, 4, 4)

资源可用数
R18
R24
R34

尝试选择P3

进程需求 (C - A)可用 (V)
P3(1, 0, 3)(8, 4, 4)

P3的需求(1, 0, 3) ≤ 可用(8, 4, 4),分配资源给P3,P3完成后释放资源:

更新后的可用向量:Available = (8, 4, 4) + (2, 1, 1) = (10, 5, 5)

资源可用数
R110
R25
R35

尝试选择P4

进程需求 (C - A)可用 (V)
P4(4, 2, 0)(10, 5, 5)

P4的需求(4, 2, 0) ≤ 可用(10, 5, 5),分配资源给P4,P4完成后释放资源:

更新后的可用向量:Available = (10, 5, 5) + (0, 0, 2) = (10, 5, 7)

最终安全序列为:P2 → P1 → P3 → P4

尝试错误选择

假设我们在P2完成后,选择进程P4而不是P1:

尝试选择P4

进程需求 (C - A)可用 (V)
P4(4, 2, 0)(7, 4, 4)

P4的需求(4, 2, 0) ≤ 可用(7, 4, 4),分配资源给P4,P4完成后释放资源:

更新后的可用向量:Available = (7, 4, 4) + (0, 0, 2) = (7, 4, 6)

资源可用数
R17
R24
R36

尝试选择P1

进程需求 (C - A)可用 (V)
P1(2, 2, 2)(7, 4, 6)

P1的需求(2, 2, 2) ≤ 可用(7, 4, 6),分配资源给P1,P1完成后释放资源:

更新后的可用向量:Available = (7, 4, 6) + (1, 0, 0) = (8, 4, 6)

资源可用数
R18
R24
R36

尝试选择P3

进程需求 (C - A)可用 (V)
P3(1, 0, 3)(8, 4, 6)

P3的需求(1, 0, 3) ≤ 可用(8, 4, 6),分配资源给P3,P3完成后释放资源:

更新后的可用向量:Available = (8, 4, 6) + (2, 1, 1) = (10, 5, 7)

最后安全序列为:P2 → P4 → P1 → P3

然而,如果我们选择了一个不合适的进程,比如:

不合适选择的情况

假设我们在P2完成后,选择P3而不是P1或P4:

进程需求 (C - A)可用 (V)
P3(1, 0, 3)(7, 4, 4)

P3的需求(1, 0, 3) ≤ 可用(7, 4, 4),分配资源给P3,P3完成后释放资源:

更新后的可用向量:Available = (7, 4, 4) + (2, 1, 1) = (9, 5, 5)

此时:

尝试选择P1

进程需求 (C - A)可用 (V)
P1(2, 2, 2)(9, 5, 5)

P1的需求(2, 2, 2) ≤ 可用(9, 5, 5),分配资源给P1,P1完成后释放资源:

更新后的可用向量:Available = (9, 5, 5) + (1, 0, 0) = (10, 5, 5)

最后选择P4:

尝试选择P4

进程需求 (C - A)可用 (V)
P4(4, 2, 0)(10, 5, 5)

P4的需求(4, 2, 0) ≤ 可用(10, 5, 5),分配资源给P4,P4完成后释放资源:

更新后的可用向量:Available = (10, 5, 5) + (0, 0, 2) = (10, 5, 7)

安全序列 P2 -> P3 -> P1 -> P4

银行家算法允许我们在选择进程后,验证整个系统是否在安全状态下运行,如果发现选择的某个进程会导致死锁或无法完成任务,则回到安全点,重新选择其他进程,从而确保系统安全运行。🎉