介绍
银行家算法是一个避免死锁的算法,用银行家放贷作为示例,银行家在放贷是需要评估放贷后是否还有能力为其他用户放贷,如果能做到则可以放贷给用户,否则则收回。
在计算机中也是类似的操作,操作系统给进程分配资源时,需要评估分配完资源后整个系统是否还能够正常运行。
整个算法由三部分组成:算法所用的数据结构,算法整体描述,安全性检测子算法。
数据结构
整个算法的数据结构包含以下几部分:
- 可用资源向量 available: 记录操作系统中各类资源的可利用数目。
- 最大需求矩阵 max: 记录每个进程对各类资源的最大需求量。
- 分配矩阵 allocation: 记录每个进程当前对各类资源的占有量。
- 需求矩阵 need: 记录每个进程对各类资源尚需的数目,等于最大需求矩阵与分配矩阵的差。
- 请求向量 request: 记录某个向量对当前各类资源的申请量。
由于这些数据结构都是记录各类资源数目的向量,我们可以预先定义一个向量类,方便后续算法中使用向量类进行计算。
class Vector extends Array {
constructor(...args) {
super(...args)
}
// vec - 当前向量后是否安全,如果出现负数则不安全
safe(vec) {}
// 当前向量 + vec
add(vec) {}
// 当前向量 - vec
sub(vec) {}
}
算法整体描述
整个算法共六个步骤:
- 若request > need,即申请的资源大于需求的资源,则进程出错。
- 若request > available,即申请的资源大于可利用资源,则进程阻塞。
- 系统尝试分配资源,对修改数据:available - request, allocation + request, need - request。
- 执行安全性检测子算法,判断分配后系统是否安全。
- 若第四步返回值为true,则分配成功,完成分配。
- 否则,撤销此次分配,将进程阻塞。
安全性检测
安全性检测就是寻找安全序列的过程,如果能找到一个安全序列就返回true,否则返回false。
安全序列
首先解释一下什么是安全序列:
系统在某一个时刻的安全状态可能不唯一,因为不同的进程顺序可能会导致不同的结果,但是不影响系统安全性的判断。
银行家算法其实就是尽可能保证系统在资源分配后不进入不安全状态,以此来避免死锁的发生。
算法描述
安全性检测算法的数据结构与银行家算法的基本相同,但是加入了两个新的数据结构:
- 工作向量 work: 他是available向量的一个临时拷贝用于简化分配撤销的实现。
- 可完成标签向量 finish: 记录进程是否可以完成,或者是否可以进入安全序列。
算法的执行过程为:
- 初始化work = available; finish = false;
- 按照进程编号顺序寻找可以加入安全序列的进程,设置finish = true和work += allocation,然后重复执行这一步。
- 若所有的进程可完成标志finish 为 true, 则返回true。
- 否则返回false,表示不安全。
代码实现
class Vector extends Array {
static len = null;
constructor(...args) {
super(...args);
if(this.len === null) {
this.len = args.length;
}
if(this.len !== args.length) {
throw new Error('Vector length mismatch');
}
}
safe(vec) {
let safe = true;
for(let i = 0; i < this.len; i++) {
if(this[i] < vec[i]) {
safe = false;
break;
}
}
return safe;
}
add(vec) {
if(vec.len !== this.len) {
throw new Error('Vector length mismatch');
}
for(let i = 0; i < this.len; i++) {
this[i] += vec[i];
}
return this;
}
sub(vec) {
if(vec.len !== this.len) {
throw new Error('Vector length mismatch');
}
for(let i = 0; i < this.len; i++) {
this[i] -= vec[i];
}
return this;
}
}
const available = new Vector(3, 3, 2);
class Progess {
constructor(name, max, allocation, need) {
this.name = name;
this.max = max;
this.allocation = allocation;
this.need = need;
}
// 申请资源
request(vec) {
// 检查请求和合理性, 如果请求超出需求则不合理
if(!vec.safe(this.need)) {
throw new Error('Request exceeds need');
}
// 检查资源是否足够,如果请求超出可用则不合理
if(!vec.safe(available)) {
throw new Error('Request exceeds available');
}
// 尝试分配,可用减去请求资源,分配加请求资源,需求减去请求资源
available.sub(vec);
this.allocation.add(vec);
this.need.sub(vec);
// 检查分配后是否安全,如果安全则分配完成,否则撤销分配然后阻塞
if(isSafe()) {
return true;
} else {
this.release(vec);
throw new Error('Request denied');
}
}
// 释放资源
release(vec) {
// 释放资源,可用加释放资源,分配减释放资源,需求加释放资源
available.add(vec);
this.allocation.sub(vec);
this.need.add(vec);
}
}
function isSafe() {
// 初始化available向量的拷贝work向量 和 代表每个进程是否能完成的的finish向量
const work = available.slice();
const finish = new Vector(ps.length).fill(false);
// 寻找一个安全序列
while(1) {
let cnt = finish.filter(f => f).length;
for(let i = 0; i < ps.length; i++) {
if(finish[i]) continue;
if(ps[i].need.safe(work)) {
work.add(ps[i].allocation);
finish[i] = true;
break;
}
}
// 如果所有进程的finish标签都是true,则找到了一个安全序列,结束循环
if(finish.every(f => f)) {
break;
}
// 如果本次循环没有修改任何进程的finish标签,则说明不存在安全序列,结束循环
if(cnt === finish.filter(f => f).length) {
break;
}
}
// 如果所有进程的标签都为true,则安全,否则不安全
return ps.every(p => p.finish);
}
const p1 = new Progess('P1', new Vector(7, 5, 3), new Vector(0, 1, 0), new Vector(7, 4, 3));
const p2 = new Progess('P2', new Vector(3, 2, 2), new Vector(2, 0, 0), new Vector(1, 2, 2));
const p3 = new Progess('P3', new Vector(9, 0, 2), new Vector(2, 2, 0), new Vector(6, 0, 6));
const ps = [p1, p2, p3];