O07-银行家算法

0 阅读4分钟

银行家算法 🏦

本文档系统介绍银行家算法的核心原理、数据结构、安全性检查机制和完整的 Python 实现。通过理论与实践相结合的方式,帮助读者深入理解操作系统中死锁避免的经典算法 🔐

章节阅读路线图 🗺️

  1. 算法概述 → 理解银行家算法的核心思想和应用场景
  2. 核心数据结构 → 掌握四大关键数据结构及其关系
  3. 安全性检查算法 → 学习如何判断系统是否处于安全状态
  4. 资源请求算法 → 理解进程资源请求的处理流程
  5. 总结 → 回顾核心要点

1. 算法概述 📖

本章介绍银行家算法的核心思想、历史背景和应用场景

1.1 什么是银行家算法?

银行家算法(Banker's Algorithm)是一个避免死锁的著名算法,由荷兰计算机科学家艾兹赫尔·戴克斯特拉(Edsger W. Dijkstra)在 1965 年为 T.H.E 操作系统设计。该算法以银行借贷系统的分配策略为基础,通过预防性措施来确保系统的安全性,判断并保证系统始终处于安全状态。

核心思想:操作系统在分配资源前,先模拟资源分配的过程,判断分配后系统是否仍处于安全状态。如果安全则分配,否则让进程等待。

类比理解

想象一个银行场景:

  • 银行(操作系统)管理着有限的资金(资源)
  • 客户(进程)申请贷款(资源请求)
  • 银行家在批准贷款前会思考:"如果我把这笔钱贷出去,金库里剩余的钱是否还能满足至少一个客户的全部需求?"
  • 如果可以,说明系统是安全的,可以批准贷款
  • 如果不行,说明可能导致"挤兑"(死锁),必须拒绝或让客户等待

这就是银行家算法"谨慎的慷慨"设计哲学——既想最大限度地把资源分配出去提高系统利用率,又必须确保在任何时候都能避免死锁。

1.2 为什么需要银行家算法?

在操作系统中,多个进程并发执行时需要共享系统资源。如果资源分配不当,可能导致死锁(Deadlock)——多个进程互相等待对方释放资源,导致所有进程都无法继续执行。

死锁产生的四个必要条件

  1. 互斥条件:资源一次只能被一个进程使用
  2. 占有并等待:进程已占有部分资源,同时等待其他资源
  3. 不可抢占:资源不能被强制从进程中夺走
  4. 循环等待:存在一个进程等待环路

银行家算法通过破坏循环等待条件来避免死锁,它在资源分配前进行安全性检查,确保系统始终处于安全状态。

1.3 安全状态与安全序列

安全状态:如果系统存在至少一个进程执行序列,使得按照该序列依次为每个进程分配其所需资源,所有进程都能顺利完成,则称系统处于安全状态。

安全序列:一个进程序列 P1,P2,,Pn\langle P_1, P_2, \ldots, P_n \rangle,如果对于每个进程 PiP_i,其后续所需的资源都能被系统当前可用资源(加上之前已完成进程释放的资源)所满足,则该序列称为安全序列。

关键结论

  • 系统处于安全状态 \Rightarrow 不会发生死锁
  • 系统处于不安全状态 \Rightarrow 可能发生死锁(不一定会发生)
  • 银行家算法的目标:确保系统始终处于安全状态

参考资料:


2. 核心数据结构 📊

本章详解银行家算法的四个关键数据结构及其数学关系

为了实现银行家算法,系统中必须设置四个核心数据结构,分别描述系统中可利用的资源、所有进程对资源的最大需求、系统中的资源分配情况,以及所有进程还需要多少资源。

假设系统中有:

  • nn 个进程:P0,P1,,Pn1P_0, P_1, \ldots, P_{n-1}
  • mm 类资源:R1,R2,,RmR_1, R_2, \ldots, R_m

2.1 可用资源向量(Available)

定义:一个长度为 mm 的一维数组,表示系统中每类资源当前的可用数量。

Available=[v1,v2,,vm]\text{Available} = [v_1, v_2, \ldots, v_m]

其中 vjv_j 表示第 jj 类资源的可用数量。

示例

Available = [10, 5, 7]  # 系统有 3 类资源:A 类 10 个,B 类 5 个,C 类 7 个

动态变化

  • 当分配资源给进程时:Available[j]=Available[j]分配数量\text{Available}[j] = \text{Available}[j] - \text{分配数量}
  • 当进程释放资源时:Available[j]=Available[j]+释放数量\text{Available}[j] = \text{Available}[j] + \text{释放数量}

2.2 最大需求矩阵(Max)

定义:一个 n×mn \times m 的二维矩阵,表示每个进程对每类资源的最大需求量。

Max=[Max0,1Max0,2Max0,mMax1,1Max1,2Max1,mMaxn1,1Maxn1,2Maxn1,m]\text{Max} = \begin{bmatrix} \text{Max}_{0,1} & \text{Max}_{0,2} & \cdots & \text{Max}_{0,m} \\ \text{Max}_{1,1} & \text{Max}_{1,2} & \cdots & \text{Max}_{1,m} \\ \vdots & \vdots & \ddots & \vdots \\ \text{Max}_{n-1,1} & \text{Max}_{n-1,2} & \cdots & \text{Max}_{n-1,m} \end{bmatrix}

其中 Maxi,j\text{Max}_{i,j} 表示进程 PiP_i 对资源 RjR_j 的最大需求量。

示例

Max = [
    [7, 5, 3],  # P0 对 A、B、C 的最大需求分别是 7、5、3
    [3, 2, 2],  # P1 对 A、B、C 的最大需求分别是 3、2、2
    [9, 0, 2],  # P2 对 A、B、C 的最大需求分别是 9、0、2
    [2, 2, 2],  # P3 对 A、B、C 的最大需求分别是 2、2、2
    [4, 3, 3]   # P4 对 A、B、C 的最大需求分别是 4、3、3
]

重要假设:进程必须在开始执行前声明其对每类资源的最大需求量,这是银行家算法的前提条件。


2.3 已分配矩阵(Allocation)

定义:一个 n×mn \times m 的二维矩阵,表示当前已分配给每个进程的每类资源数量。

Allocation=[Alloc0,1Alloc0,2Alloc0,mAlloc1,1Alloc1,2Alloc1,mAllocn1,1Allocn1,2Allocn1,m]\text{Allocation} = \begin{bmatrix} \text{Alloc}_{0,1} & \text{Alloc}_{0,2} & \cdots & \text{Alloc}_{0,m} \\ \text{Alloc}_{1,1} & \text{Alloc}_{1,2} & \cdots & \text{Alloc}_{1,m} \\ \vdots & \vdots & \ddots & \vdots \\ \text{Alloc}_{n-1,1} & \text{Alloc}_{n-1,2} & \cdots & \text{Alloc}_{n-1,m} \end{bmatrix}

其中 Alloci,j\text{Alloc}_{i,j} 表示当前分配给进程 PiP_i 的资源 RjR_j 的数量。

示例

Allocation = [
    [0, 1, 0],  # P0 已分配 A:0, B:1, C:0
    [2, 0, 0],  # P1 已分配 A:2, B:0, C:0
    [3, 0, 2],  # P2 已分配 A:3, B:0, C:2
    [2, 1, 1],  # P3 已分配 A:2, B:1, C:1
    [0, 0, 2]   # P4 已分配 A:0, B:0, C:2
]

2.4 需求矩阵(Need)

定义:一个 n×mn \times m 的二维矩阵,表示每个进程还需要的每类资源数量。

Need=[Need0,1Need0,2Need0,mNeed1,1Need1,2Need1,mNeedn1,1Needn1,2Needn1,m]\text{Need} = \begin{bmatrix} \text{Need}_{0,1} & \text{Need}_{0,2} & \cdots & \text{Need}_{0,m} \\ \text{Need}_{1,1} & \text{Need}_{1,2} & \cdots & \text{Need}_{1,m} \\ \vdots & \vdots & \ddots & \vdots \\ \text{Need}_{n-1,1} & \text{Need}_{n-1,2} & \cdots & \text{Need}_{n-1,m} \end{bmatrix}

其中 Needi,j\text{Need}_{i,j} 表示进程 PiP_i 还需要资源 RjR_j 的数量。

核心公式

Needi,j=Maxi,jAllocationi,j\text{Need}_{i,j} = \text{Max}_{i,j} - \text{Allocation}_{i,j}

即:还需资源 = 最大需求 - 已分配资源

示例计算

# Need = Max - Allocation
Need = [
    [7-0, 5-1, 3-0],   # P0: [7, 4, 3]
    [3-2, 2-0, 2-0],   # P1: [1, 2, 2]
    [9-3, 0-0, 2-2],   # P2: [6, 0, 0]
    [2-2, 2-1, 2-1],   # P3: [0, 1, 1]
    [4-0, 3-0, 3-2]    # P4: [4, 3, 1]
]

2.5 数据结构关系总结

四个数据结构之间的关系可以用下图表示:

Max(最大需求)
  ↓
  ↓ 减去
  ↓
Allocation(已分配)
  ↓
  ↓ 等于
  ↓
Need(还需资源)

Available(可用资源)独立维护,随分配和回收动态变化

数学关系

i=0n1Allocationi,j+Availablej=系统资源总量j\sum_{i=0}^{n-1} \text{Allocation}_{i,j} + \text{Available}_j = \text{系统资源总量}_j

即:某类资源的已分配总量 + 可用量 = 系统该类资源的总量。


参考资料:


3. 安全性检查算法 🔍

本章详解如何判断系统是否处于安全状态

安全性检查算法是银行家算法的核心,用于判断当前系统状态是否安全。如果安全,算法还会找出一个安全序列。

3.1 算法流程

安全性检查算法的执行步骤如下:

步骤 1:初始化

设置两个辅助向量:

  • Work\text{Work}:长度为 mm 的工作向量,表示系统当前可提供的资源数量,初始时 Work=Available\text{Work} = \text{Available}
  • Finish\text{Finish}:长度为 nn 的布尔向量,表示每个进程是否能完成,初始时所有 Finish[i]=False\text{Finish}[i] = \text{False}

步骤 2:寻找可执行进程

从进程集合中寻找一个满足以下条件的进程 PiP_i

Finish[i]=FalseNeediWork\text{Finish}[i] = \text{False} \quad \text{且} \quad \text{Need}_i \leq \text{Work}

即:该进程尚未完成,且其所需资源不超过系统当前可用资源。

如果找到这样的进程,转到步骤 3;如果找不到,转到步骤 4。

步骤 3:模拟进程执行完成

假设找到进程 PiP_i,模拟其执行并完成:

Work=Work+AllocationiFinish[i]=True\begin{align*} \text{Work} &= \text{Work} + \text{Allocation}_i \\ \text{Finish}[i] &= \text{True} \end{align*}

即将该进程已分配的资源释放回系统,然后返回步骤 2 继续寻找下一个可执行进程。

步骤 4:判断安全性

检查是否所有进程都能完成:

如果所有 Finish[i]=True,则系统处于安全状态\text{如果所有 } \text{Finish}[i] = \text{True} \text{,则系统处于安全状态}

否则,系统处于不安全状态。

3.2 算法伪代码

function is_safe():
    Work = Available.copy()                     # 工作向量初始化为可用资源
    Finish = [False] * n                        # 所有进程标记为未完成
    safe_sequence = []                          # 安全序列
    
    while True:
        found = False                           # 标记本轮是否找到可执行进程
        
        for i in range(n):                      # 遍历所有进程
            if not Finish[i] and Need[i] <= Work:  # 进程未完成且资源足够
                Work = Work + Allocation[i]     # 模拟进程释放资源
                Finish[i] = True                # 标记进程已完成
                safe_sequence.append(i)         # 加入安全序列
                found = True                    # 标记找到进程
        
        if not found:                           # 本轮没找到进程,退出循环
            break
    
    if all(Finish):                             # 所有进程都能完成
        return True, safe_sequence              # 系统安全,返回安全序列
    else:
        return False, []                        # 系统不安全

3.3 算法复杂度分析

时间复杂度O(n2×m)O(n^2 \times m)

  • 外层循环最多执行 nn 次(每次找到一个进程)
  • 内层循环遍历 nn 个进程
  • 向量比较和加法操作需要 O(m)O(m) 时间

空间复杂度O(n+m)O(n + m)

  • Work\text{Work} 向量:O(m)O(m)
  • Finish\text{Finish} 向量:O(n)O(n)
  • 安全序列:O(n)O(n)

3.4 安全性检查示例

假设系统状态如下:

Available = [10, 5, 7]

Max = [
    [7, 5, 3],  # P0
    [3, 2, 2],  # P1
    [9, 0, 2],  # P2
    [2, 2, 2],  # P3
    [4, 3, 3]   # P4
]

Allocation = [
    [0, 1, 0],  # P0
    [2, 0, 0],  # P1
    [3, 0, 2],  # P2
    [2, 1, 1],  # P3
    [0, 0, 2]   # P4
]

Need = [
    [7, 4, 3],  # P0
    [1, 2, 2],  # P1
    [6, 0, 0],  # P2
    [0, 1, 1],  # P3
    [4, 3, 1]   # P4
]

安全性检查执行过程

步骤Work找到进程Need ≤ Work?新 Work = Work + AllocationFinish
初始[10, 5, 7]---[F,F,F,F,F]
1[10, 5, 7]P1[1,2,2] ≤ [10,5,7] ✓[10,5,7] + [2,0,0] = [12,5,7][F,T,F,F,F]
2[12, 5, 7]P3[0,1,1] ≤ [12,5,7] ✓[12,5,7] + [2,1,1] = [14,6,8][F,T,F,T,F]
3[14, 6, 8]P4[4,3,1] ≤ [14,6,8] ✓[14,6,8] + [0,0,2] = [14,6,10][F,T,F,T,T]
4[14, 6, 10]P0[7,4,3] ≤ [14,6,10] ✓[14,6,10] + [0,1,0] = [14,7,10][T,T,F,T,T]
5[14, 7, 10]P2[6,0,0] ≤ [14,7,10] ✓[14,7,10] + [3,0,2] = [17,7,12][T,T,T,T,T]

结果:所有 Finish[i]=True\text{Finish}[i] = \text{True},系统处于安全状态。

安全序列P1,P3,P4,P0,P2\langle P_1, P_3, P_4, P_0, P_2 \rangle

(注意:安全序列不唯一,其他安全序列如 P3,P4,P1,P0,P2\langle P_3, P_4, P_1, P_0, P_2 \rangle 也可能存在)


参考资料:


4. 资源请求算法 📥

本章详解进程请求资源时的处理流程

当进程 PiP_i 发出资源请求 Requesti\text{Request}_i 时,银行家算法按以下步骤处理:

4.1 算法流程

步骤 1:检查请求合法性

检查进程 PiP_i 的请求是否合法:

RequestiNeedi\text{Request}_i \leq \text{Need}_i

如果请求超过其声明的最大需求,则拒绝请求(进程行为异常)。

步骤 2:检查资源可用性

检查系统是否有足够的可用资源:

RequestiAvailable\text{Request}_i \leq \text{Available}

如果可用资源不足,则让进程 PiP_i 等待。

步骤 3:试探性分配

假设系统满足该请求,修改数据结构:

Available=AvailableRequestiAllocationi=Allocationi+RequestiNeedi=NeediRequesti\begin{align*} \text{Available} &= \text{Available} - \text{Request}_i \\ \text{Allocation}_i &= \text{Allocation}_i + \text{Request}_i \\ \text{Need}_i &= \text{Need}_i - \text{Request}_i \end{align*}

步骤 4:执行安全性检查

调用安全性检查算法,判断新的系统状态是否安全:

  • 如果安全:正式分配资源给进程 PiP_i
  • 如果不安全:恢复原状态,让进程 PiP_i 等待

4.2 算法伪代码

function request_resources(i, Request_i):
    # 步骤1:检查请求是否超过需求
    if Request_i > Need[i]:
        return "错误:请求超过最大需求"
    
    # 步骤2:检查可用资源是否足够
    if Request_i > Available:
        return "等待:可用资源不足"
    
    # 步骤3:试探性分配
    Available = Available - Request_i
    Allocation[i] = Allocation[i] + Request_i
    Need[i] = Need[i] - Request_i
    
    # 步骤4:安全性检查
    safe, sequence = is_safe()
    
    if safe:
        return f"分配成功,安全序列:{sequence}"
    else:
        # 恢复原状态
        Available = Available + Request_i
        Allocation[i] = Allocation[i] - Request_i
        Need[i] = Need[i] + Request_i
        return "拒绝:分配后系统不安全"

4.3 资源请求示例

继续使用前面的系统状态,假设进程 P1P_1 发出请求 Request1=[1,0,2]\text{Request}_1 = [1, 0, 2]

步骤 1:检查请求合法性

Request1=[1,0,2]Need1=[1,2,2]\text{Request}_1 = [1, 0, 2] \leq \text{Need}_1 = [1, 2, 2] \quad \checkmark

请求合法。

步骤 2:检查资源可用性

Request1=[1,0,2]Available=[10,5,7]\text{Request}_1 = [1, 0, 2] \leq \text{Available} = [10, 5, 7] \quad \checkmark

资源充足。

步骤 3:试探性分配

Available = [10, 5, 7] - [1, 0, 2] = [9, 5, 5]
Allocation[1] = [2, 0, 0] + [1, 0, 2] = [3, 0, 2]
Need[1] = [1, 2, 2] - [1, 0, 2] = [0, 2, 0]

步骤 4:安全性检查

使用新的系统状态执行安全性检查,如果能找到安全序列,则正式分配;否则拒绝请求并恢复原状态。


参考资料:


5. 总结 📝

银行家算法是操作系统中避免死锁的经典算法,核心要点回顾:

组件作用关键公式/操作
Available记录可用资源动态变化:分配时减,回收时加
Max记录最大需求进程启动时声明
Allocation记录已分配资源分配时增加,回收时清零
Need记录还需资源Need=MaxAllocation\text{Need} = \text{Max} - \text{Allocation}
安全性检查判断系统是否安全寻找安全序列 P1,,Pn\langle P_1, \ldots, P_n \rangle
资源请求处理进程请求检查 → 试探分配 → 安全检查 → 决定

🔴 关键理解

  • 银行家算法的核心:在分配资源前进行安全性检查,确保系统始终处于安全状态
  • 安全状态 ≠ 不会死锁:安全状态保证不会死锁,不安全状态可能导致死锁(不一定会发生)
  • 算法的前提:进程必须预先声明最大需求,这在实际系统中可能难以满足
  • 算法的代价:每次资源请求都需要执行安全性检查,时间复杂度 O(n2×m)O(n^2 \times m)
  • 实际应用:虽然完全照搬银行家算法的场景不多,但其"分配前检查"的思想渗透在现代系统的资源管理中

最后更新时间:2026-06-02