新闻详情

新闻详情

首页 / 资讯中心 / 详情

银行家算法详解:从死锁避免到现代系统资源分配实践

发布时间:2026/9/29 16:15:36来源:尧图网络
银行家算法详解:从死锁避免到现代系统资源分配实践
1. 死锁与银行家算法先搞明白我们要解决什么问题说起银行家算法很多人的第一反应是大学操作系统课上的噩梦或者是系统架构设计师考试里那道让人挠头的案例分析题。我当年学的时候也是背了又忘、忘了又背直到后来在真实项目里做资源调度、优化数据库事务并发、设计分布式锁才真正理解这个算法的价值——它不是一道考试题而是一套非常优雅的“先预测、后决策”的资源分配思想。银行家算法本质上解决的是死锁避免问题。死锁听起来抽象用生活场景类比一下就明白两个人过独木桥迎面相遇谁都不肯退结果两个人都过不去。放到计算机系统里就是多个进程各自持有一些资源——数据库连接、内存块、文件句柄——同时又在等待对方手里的资源结果大家全部卡死谁也跑不动。要说清楚银行家算法得先聊清楚死锁产生的四个必要条件互斥条件、持有并等待、不可剥夺和循环等待。这四个条件必须同时成立死锁才可能发生。所以系统设计上有两条路线一条是“预防”想办法破坏这四个条件中的某一个另一条是“避免”在资源分配之前先做可行性评估这一步的核心就是银行家算法。这篇文章适合谁看我认为三类人最需要。第一类是准备系统架构设计师认证考试的朋友案例分析几乎年年都能看到资源分配的影子第二类是正在设计中间件、数据库、嵌入式系统比如STM32这类资源受限设备的研发人员第三类是想把操作系统经典原理真正吃透的在校学生。我会把完整的数据结构、安全性检查流程、一个能手工推演的案例、一份可运行的 Python 参考实现以及这个算法在现代架构里到底还能不能用的实战经验一起讲透。1.1 为什么“避免”比“预防”更灵活在展开具体算法之前先区分两个经常被搞混的概念死锁预防和死锁避免。死锁预防是从根子上破坏必要条件比如要求进程一次性申请所有资源或者规定资源可以被剥夺。这种思路简单粗暴但副作用很大一次性申请容易造成资源浪费因为进程很少会同时用到所有资源可剥夺在某些场景下根本做不到比如一个进程正在写文件你没法强行把文件句柄收走。死锁避免则聪明得多。它不限制进程怎么申请资源而是在每次分配前用算法判断这次分配之后系统是否还能保证所有进程最终跑完。能保证就批准不能保证就让请求方继续等待。银行家算法就是这种“动态检查”的代表实现名字来源于银行放贷逻辑银行不能因为客户有需求就把钱全贷出去必须留出足够储备金保证其他客户同时来取钱时也能兑付。操作系统里的进程和资源本质上就是客户和资金的关系。注意银行家算法属于“死锁避免”和“死锁预防”不是一回事。预防是静态规则简单但笨拙避免是动态决策精细但需要额外计算开销。1.2 “安全状态”和“安全序列”是理解一切的钥匙银行家算法有两个核心概念安全状态和安全序列。一个状态是安全的指系统存在某种执行顺序能把所有进程都执行到完成状态这个顺序就叫安全序列。不安全状态不等于死锁只是存在潜在风险——如果继续盲目分配最终很可能走进死锁。打个比方。一个停车场有10个车位现在停了7辆还剩3个空位门口有5辆车排队。如果管理员知道每辆车预计停多久就能判断现在放几辆进去是安全的如果贪心一次性把5辆全放进去车位用完后来的车堵在门口先到的车也出不来整个停车场瘫痪。银行家算法里的“安全序列”就是管理员脑中编排好的进场-出场顺序。只要这个顺序存在系统就转得动找不到这个顺序就必须踩刹车。2. 银行家算法的数据结构与核心流程拆解2.1 四张表Available、Max、Allocation、Need银行家算法的运行前提是系统里所有资源数量固定、所有进程数量固定而且每个进程必须提前声明自己最多需要多少资源。在这个前提下算法维护四张核心数据表。Available可用资源一个长度为 m 的向量m 是资源类型数记录当前每种资源还有多少可用。比如 Available (3, 3, 2)表示 A 类资源剩 3 个B 类剩 3 个C 类剩 2 个。Max最大需求n 行 m 列的矩阵n 是进程数记录每个进程在整个生命周期里最多需要多少资源。Allocation已分配n 行 m 列的矩阵记录当前每个进程已经拿到了多少资源。Need还需资源n 行 m 列的矩阵记录每个进程还差多少资源才能达到最大需求。四张表之间有一个铁律Need Max - Allocation。这不是额外存储而是计算出来的。做任何请求判断时第一件事就是拿这个公式核对数据一致性。我在实际项目里写过类似的资源管理模块深深体会到公式本身不难难的是矩阵数据在并发场景下怎么保真后面会详细说。除了这四个矩阵还有一个 Work 向量和一个 Finish 数组。Work 是安全性检查过程中的“可用资源副本”初始等于 Available随着模拟执行的进程不断累加回收资源。Finish 数组记录每个进程是否已模拟执行完毕初始全为 false。2.2 安全性检查算法模拟一遍“全部跑完”安全性检查是银行家算法的地基。它的思路是假设系统现在把所有可用资源先冻结然后挑一个还能满足全部需求的进程把它先“虚拟执行完”回收它占用的资源再看看剩下进程里有没有能继续执行的一直循环。如果所有进程都能被安排一遍说明存在安全序列状态安全如果循环到一半找不到任何能继续的进程说明状态不安全。具体步骤如下初始化 Work AvailableFinish 数组全为 false。在未完成的进程里找哪个进程 i 满足两个条件Finish[i] false 且 Need[i] 的每一个分量都不超过 Work 的对应分量。找到就模拟执行Work Work Allocation[i]Finish[i] true记录进安全序列找不到就进入第 4 步。如果所有 Finish 都为 true说明系统安全否则不安全返回无安全序列。这套模拟逻辑有点像“还债清偿”你手头有一笔流动资金先找债务最小且能一次付清的客户清掉一笔后流动资金变多再清下一笔。只要最后所有债务都能还清银行就不会破产。2.3 资源请求算法两步检查加一次试探当一个进程 P_i 发出资源请求 Request 向量时处理流程分三步合理性检查Request 的每个分量都必须小于等于 Need[i] 的对应分量。如果请求量大于自己的最大需求直接拒绝因为这不合法。可得性检查Request 的每个分量必须小于等于 Available 的对应分量。如果当前可用资源都不够肯定不能批准让进程等待。试探分配假设批准这个请求先临时修改 Available、Allocation、Need再调用安全性检查算法。这一步是关键——如果试探后的状态是安全的正式批准如果不安全把三个矩阵全回滚回去拒绝本次请求。这个“两步检查加一次试探”的模式就是银行家算法区别于其他资源分配策略的核心它不只看“现在够不够”更看“给了以后整个系统会不会出问题”。很像投资人评估项目不只看这笔钱投出去亏不亏更要看投完手里的现金流会不会断。3. 完整案例手工推演从安全到拒绝的全过程3.1 初始状态5 个进程、3 类资源书本上的经典案例有点绕我自己构造一个既真实又容易手工检验的例子。假设系统有 5 个进程 P0 到 P43 类资源 A、B、C总资源量分别为 (10, 5, 7)。各进程的 Max 和 Allocation 如下表所示进程Allocation已分配Max最大需求Need还需P0(0, 1, 0)(7, 5, 3)(7, 4, 3)P1(2, 0, 0)(3, 2, 2)(1, 2, 2)P2(3, 0, 2)(9, 0, 2)(6, 0, 0)P3(2, 1, 1)(2, 2, 2)(0, 1, 1)P4(0, 0, 2)(4, 3, 3)(4, 3, 1)已分配资源合计A 类 023207B 类 100102C 类 002125。总资源减已分配得到初始 Available (3, 3, 2)。这张表建议自己动手算一遍尤其是 Need 矩阵一算就懂。3.2 第一步验证初始状态是否安全初始 Available (3, 3, 2)。开始安全性检查第一轮扫描P1 的 Need (1, 2, 2)三个分量分别不超过 (3, 3, 2)选 P1。模拟执行后回收 P1 的 AllocationWork 变为 (5, 3, 2)。第二轮P3 的 Need (0, 1, 1)满足当前 Work执行后 Work 变为 (7, 4, 3)。第三轮P4 的 Need (4, 3, 1)满足执行后 Work 变为 (7, 4, 5)。第四轮P2 的 Need (6, 0, 0)A 类 6 小于 7执行后 Work 变为 (10, 4, 7)。第五轮P0 的 Need (7, 4, 3)满足全部进程执行完毕。所以初始状态安全一条常见的安全序列是 P1、P3、P4、P2、P0。注意安全序列通常不唯一第二轮如果先安排 P2 也能走通这属于正常情况。3.3 请求 1P1 申请 (1, 0, 1)批准P1 发出请求 Request (1, 0, 1)。第一步检查 Request Need。P1 的 Need 是 (1, 2, 2)三个分量 11、02、12通过。第二步检查 Request Available。当前可用 (3, 3, 2)三个分量 13、03、12通过。第三步试探分配。临时把 P1 的 Allocation 改为 (3, 0, 1)Need 改为 (0, 2, 1)Available 变为 (2, 3, 1)然后做安全性检查。Work 从 (2, 3, 1) 开始P1 自己先满足执行后 Work 到 (5, 3, 2)然后 P3、P4、P2、P0 依次执行全部都能完成。试探后的状态依然安全正式批准请求。3.4 请求 2P0 申请 (0, 0, 1)拒绝紧接着P0 又发出请求 Request (0, 0, 1)。此时系统可用资源已经变成 (2, 3, 1)。第一步Request NeedP0 的 Need 是 (7, 4, 3)通过。第二步Request Available(0, 0, 1) (2, 3, 1)通过。第三步试探分配。临时把 P0 的 Allocation 改为 (0, 1, 1)Need 改为 (7, 4, 2)Available 变成 (2, 3, 0)。开始安全性检查Work (2, 3, 0)扫描所有进程P0 的 Need 要 7 个 A 类资源不行P1 的 Need 要 1 个 C 类资源但 C 类已经为 0P2 要 6 个 A 类不行P3 要 1 个 C 类也不行P4 要 1 个 C 类还是不行。整个循环跑完一个进程都推进不了。试探后的状态不安全必须回滚拒绝 P0 的请求。这个过程非常直观地展示了“资源够但给了会出事”的情况C 类资源本来就剩最后 1 个它是 P1、P3、P4 继续推进的关键P0 虽然只想要这 1 个但拿走之后系统就转不动了。4. 用 Python 实现银行家算法一份可直接改造的参考代码4.1 安全性检查函数手工推演做的事代码里可以精确表达。下面这份 Python 代码我尽量写得贴近算法描述方便你对照理解后改写成其他语言。核心就是两个循环外层循环控制执行轮次内层循环扫描未完成的进程。def safety_check(available, allocation, need): n len(allocation) # 进程数 m len(available) # 资源类型数 work available[:] # Work 初始等于 Available finish [False] * n # 所有进程未完成 safe_seq [] while len(safe_seq) n: found False for i in range(n): if not finish[i] and all(need[i][j] work[j] for j in range(m)): # 模拟执行进程 i回收它占用的资源 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这个函数有个细节值得注意内层循环每次从 P0 开始扫描所以同一个状态下可能先找到编号小的进程。这不会影响正确性但会导致安全序列输出固定化。如果你希望序列更随机可以把 range(n) 换成打乱后的顺序。4.2 资源请求处理函数请求处理的核心是“先试探后回滚”。Python 里可以用深拷贝保存原状态判断不安全时直接还原比逐个矩阵改来改去更不容易出错。def request_resources(pid, req, available, allocation, need): m len(available) # 第一步请求不能超过进程的最大需求 for j in range(m): if req[j] need[pid][j]: print(fP{pid} 请求 {req} 超过最大需求拒绝) return False # 第二步请求不能超过当前可用资源 for j in range(m): if req[j] available[j]: print(fP{pid} 请求 {req} 超过可用资源等待) return False # 第三步试探分配 tmp_avail available[:] tmp_alloc [row[:] for row in allocation] tmp_need [row[:] for row in need] for j in range(m): tmp_avail[j] - req[j] tmp_alloc[pid][j] req[j] tmp_need[pid][j] - req[j] safe, seq safety_check(tmp_avail, tmp_alloc, tmp_need) if safe: # 试探安全正式修改三个矩阵 for j in range(m): available[j] - req[j] allocation[pid][j] req[j] need[pid][j] - req[j] print(fP{pid} 请求 {req} 批准安全序列{seq}) return True else: print(fP{pid} 请求 {req} 会导致不安全状态拒绝) return False4.3 跑一遍完整流程把第 3 节的案例数据填进去整体跑一遍available [3, 3, 2] allocation [ [0, 1, 0], [2, 0, 0], [3, 0, 2], [2, 1, 1], [0, 0, 2], ] max_need [ [7, 5, 3], [3, 2, 2], [9, 0, 2], [2, 2, 2], [4, 3, 3], ] need [[max_need[i][j] - allocation[i][j] for j in range(3)] for i in range(5)] print(初始安全性检查, safety_check(available, allocation, need)) request_resources(1, [1, 0, 1], available, allocation, need) request_resources(0, [0, 0, 1], available, allocation, need)输出结果应该和手工推演一致初始安全序列存在P1 请求批准P0 请求拒绝。拿到这个代码后你还可以自己改改数据比如把进程数增加到 10 个、资源类型增加到 5 类验证算法的通用性。5. 常见坑位与真实架构里的经验判断5.1 面试和考试里最容易踩的四个坑银行家算法看似简单但我在带新人和面试交流时发现至少有四个坑经常有人踩。第一个坑是把“死锁避免”和“死锁预防”混为一谈。很多人在描述里写了“破坏互斥条件”之类的话然后把银行家算法归类进去这是硬伤。银行家算法属于避免不破坏任何必要条件它做的是动态决策。第二个坑是忘记检查 Request Need 就直接看 Available。如果请求量超过了进程自己的最大需求就算系统有再多资源也不能给否则 Need Max - Allocation 会变成负数整个矩阵自洽性被打破。第三个坑是安全序列不唯一带来的困惑。同一个安全状态完全可能有多条合法序列考试里只要写出一条就算对。不要因为自己和别人写的顺序不一样就以为自己错了。第四个坑是“安全状态一定不死锁不安全状态一定死锁”这个认知偏差。安全状态肯定不死锁这一点成立但不安全状态只是“可能死锁”不是“已经死锁”。算法拒绝不安全请求是为了避免未来进入死锁而不是检测到死锁已经发生。5.2 为什么主流操作系统没大规模使用银行家算法一个很实在的问题是Linux 这类通用操作系统并没有完整实现银行家算法为什么这么经典的方法反而不被采用答案和算法的三个前提假设有关。第一个假设是所有资源数量固定。真实系统里资源类型和数量会动态变化比如内存可以换入换出、连接池可以扩容资源根本不是静态的。第二个假设是每个进程必须提前知道自己最大需要多少资源。真实应用里一个进程的行为取决于用户输入、网络状态、运行时参数很难预知上限。第三个假设是进程数量固定但现代系统进程和线程不断创建销毁矩阵规模随时变化。还有一个非常现实的问题银行家算法偏向保守它为了保证安全状态会牺牲资源利用率。安全性检查的本质是“最坏情况模拟”如果一个进程在某类资源上的最大需求很大即使它平时只用一点点算法也会因为它的存在而拒绝其他进程的申请。在追求高吞吐的服务器上这种保守策略代价太高。所以实际系统更多采用“预防检测恢复”的组合拳锁设计时尽量缩短持有时间加锁顺序统一配合超时机制和死锁检测发现死锁就 kill 掉部分进程。这比银行家算法的预测模式更符合真实场景。5.3 银行家算法在现代架构里的影子与变体虽然银行家算法没有被大规模直接用它的思想却深深嵌在很多系统设计里。数据库系统中的事务管理器经常做类似的“预分析”一个事务要锁哪些行、写哪些表提前计算会不会和其他事务形成环如果可能就延迟启动事务。分布式系统中的资源调度器也有银行家算法的影子例如 YARN 里的容量调度器在给新容器分配资源时会检查队列里所有运行中作业的“资源保证”确保不打乱已有承诺。嵌入式系统反而是银行家算法最容易找到完全用武之地的地方。STM32 这类单片机上外设资源、内存块、DMA 通道数量固定任务集合在编译期就确定每个任务拿多少资源也是静态声明的完全满足算法的三个前提。很多 RTOS 的多资源分配内核就是银行家算法的微缩版。在资源极度受限且任务可预测的场景里算法的保守性反而成了优点——宁可慢一点不能崩。6. 延伸思考银行家算法教会系统架构师的几件事6.1 资源配额与超卖问题的本质做系统架构时只要涉及资源池就一定会遇到“超卖”问题。比如线程池线程数是 10却有 20 个任务在排队这就是一定程度上的允许等待但如果 20 个任务各自都持有一个连接、又在等另一个连接连接池很快就变成死锁现场。银行家算法给架构师的第一课是不要只看瞬时可用量要看整体承诺量。每一个运行中的任务都已经“承诺”了某些资源新的分配必须在这个承诺总量里做判断。很多线上事故的根因就是只检查“还有多少可用”没检查“一旦全部分发系统能否自洽”。容量规划、限流降级的设计里这一条特别重要。6.2 分布式锁与数据库事务的借鉴思路分布式系统里死锁更容易发生也更难排查。比如两个服务各持有一个分布式锁等待对方释放另一个锁一旦超时设置不当双方都卡到天荒地老。解决思路里经常能看到银行家算法的影子要么在获取锁之前做拓扑排序统一加锁顺序要么在获得锁之后先判断“我接下来需要的所有资源当前是否都可获得”不可获得就立即释放而不是等待。数据库事务里更是如此。两阶段锁协议本身就隐含了“资源请求逐步满足”的过程。死锁检测机制发现环之后选择牺牲一个事务本质上也是一种“回滚试探”的银行家思想——只是把“分配前预测”换成了“发现后补救”。6.3 什么时候该放弃预判选择快速失败最后聊一点反直觉的经验。银行家算法的核心是“预判”但预判需要极高的信息完整度。如果你所在的系统根本无法准确得知进程的最大需求那么强行使用银行家算法会变成一场灾难请求被频繁拒绝、资源利用率低下、系统吞吐暴跌。我个人的经验是在信息可靠、资源稀缺、失败代价极高的场景里银行家算法是解决死锁避免的上乘之选但在信息不确定、资源弹性伸缩、失败可以快速恢复的现代互联网架构里把“预判”换成“快速失败加重试”往往更实用。比如一个分布式任务拿了锁之后处理失败直接释放锁、重试或由监控系统介入通常比事先做一堆安全判定更高效。设计系统时最怕的不是死锁本身而是不知道系统什么时候会死锁、死锁发生后能不能恢复。能预测的场景做预测预测不了的场景做好检测和兜底这是我从银行家算法里学到的真正方法论。如果你正准备系统架构设计相关的内容建议把今天这个手动推演案例在草稿纸上完整写一遍再从矩阵出发想想自己的系统里哪些资源分配可以套用这套逻辑收获会比单纯背结论大得多。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

2026 年 AI 大模型推理服务怎么选:七家主流 API 聚合平台横向测评 2026/9/29 22:14:01

2026 年 AI 大模型推理服务怎么选:七家主流 API 聚合平台横向测评

进入 2026 年 5 月,AI 大模型推理服务市场的格局已经相当清晰。模型覆盖度、定价水平、推理速度与合规支持这四个维度上,各家平台形成了明显的分工。对开发团队而言,在动手对比参数之前,更值得先想清楚一件事:自己最需…

阅读更多 →
校园代取快递系统怎么选?业务链路与演示核对清单 2026/9/29 22:14:01

校园代取快递系统怎么选?业务链路与演示核对清单

校园代取快递系统通常不是一套独立软件,而是跑腿配送业务中的一个服务类型,与帮买、帮送、帮取、任务悬赏并列。选型时可以按“业务范围—履约链路—结算分账—部署方式—服务支持”五步逐项核对。如果计划同时经营校园外卖与代取快递、并考虑多校区扩展…

阅读更多 →
新手AI的入门必知 2026/9/29 22:14:01

新手AI的入门必知

1. 引言 随着 AI 生态的共建,AI 早已从问答知识库发展成了“全能助理”。无论是创意发展、内容生成,还是日常办公、代码编写,AI 都在扮演越来越重要的角色。然而,AI 入门看似简单,实则学问不少——从模型选择、提示词设…

阅读更多 →
GEO收录检测工具有哪些?免费GEO优化工具能用吗? 2026/9/29 22:13:55

GEO收录检测工具有哪些?免费GEO优化工具能用吗?

找 GEO 检测工具的人,通常抱着和做 SEO 时一样的预期:输个网址,出一份收录报告。现实是 AI 引擎的检索链路不提供公开的收录查询接口,工具生态还在早期。这篇讲清哪些能测、怎么测。一、检测 GEO 效果的三个免费途径途径一&#x…

阅读更多 →
2026 企业 AI 办公工具选型指南:适合团队使用的 AI 办公产品有哪些 2026/9/29 22:13:54

2026 企业 AI 办公工具选型指南:适合团队使用的 AI 办公产品有哪些

企业采购AI办公工具的过程中,很容易陷入几个典型的认知误区。不少团队拿到产品清单之后,第一反应是拉一张功能对照表,谁家标注的功能点更多就优先纳入候选池,也有团队直接参考公开的价格排序,优先选择成本最低的选项&a…

阅读更多 →
基于Vue的培训认证与就业服务平台(Java+SpringBoot+MySQL)| 毕业设计 源码+论文+完整教程 2026/9/29 22:13:54

基于Vue的培训认证与就业服务平台(Java+SpringBoot+MySQL)| 毕业设计 源码+论文+完整教程

面向餐饮行业的「培训 认证 就业」一体化服务平台:学员学课程、考认证、投岗位;企业发岗位、筛简历、做面试;管理员全盘统筹。三端闭环,一条龙打通。 源码 论文 答辩PPT 开题报告 数据库脚本 设计图源文件 部署/答辩视频教…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉