银行家算法
银行家算法的实现
以下部分内容来自百度百科:银行家算法
题目描述:
- 银行家算法(Banker’s Algorithm)是一个避免死锁(Deadlock)的著名算法,是由艾兹格·迪杰斯特拉在1965年为T.H.E系统设计的一种避免死锁产生的算法。它以银行借贷系统的分配策略为基础,判断并保证系统的安全运行。
在银行中,客户申请贷款的数量是有限的,每个客户在第一次申请贷款时要声明完成该项目所需的最大资金量,在满足所有贷款要求时,客户应及时归还。银行家在客户申请的贷款数量不超过自己拥有的最大值时,都应尽量满足客户的需要。在这样的描述中,银行家就好比操作系统,资金就是资源,客户就相当于要申请资源的进程。
银行家算法是一种最有代表性的避免死锁的算法。在避免死锁方法中允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配资源的安全性,若分配不会导致系统进入不安全状态,则分配,否则等待。为实现银行家算法,系统必须设置若干数据结构。
操作系统安全状态和不安全状态,,等价于操作系统是否存在一个安全序列。安全序列是指一个进程序列{P1,…,Pn}是安全的,即对于每一个进程Pi(1≤i≤n),它以后尚需要的资源量不超过系统当前剩余资源量与所有进程Pj (j < i )当前占有资源量之和。
数据结构
1)可利用资源向量Available
是个含有m个元素的数组,其中的每一个元素代表一类可利用的资源数目。如果Available[j]=K,则表示系统中现有Rj类资源K个。
2)最大需求矩阵Max
这是一个n×m的矩阵,它定义了系统中n个进程中的每一个进程对m类资源的最大需求。如果Max[i,j]=K,则表示进程i需要Rj类资源的最大数目为K。
3)分配矩阵Allocation
这也是一个n×m的矩阵,它定义了系统中每一类资源当前已分配给每一进程的资源数。如果Allocation[i,j]=K,则表示进程i当前已分得Rj类资源的 数目为K。
4)需求矩阵Need。
这也是一个n×m的矩阵,用以表示每一个进程尚需的各类资源数。如果Need[i,j]=K,则表示进程i还需要Rj类资源K个,方能完成其任务。
其中蕴含了关系:Need[i,j]=Max[i,j]-Allocation[i,j],因此Need,Max,Allocation,三者只需要知道两个即可。
算法原理:
我们可以把操作系统看作是银行家,操作系统管理的资源相当于银行家管理的资金,进程向操作系统请求分配资源相当于用户向银行家贷款。
为保证资金的安全,银行家规定:
(1) 当一个顾客对资金的最大需求量不超过银行家现有的资金时就可接纳该顾客;
(2) 顾客可以分期贷款,但贷款的总数不能超过最大需求量;
(3) 当银行家现有的资金不能满足顾客尚需的贷款数额时,对顾客的贷款可推迟支付,但总能使顾客在有限的时间里得到贷款;
(4) 当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金.
操作系统按照银行家制定的规则为进程分配资源,当进程首次申请资源时,要测试该进程对资源的最大需求量,如果系统现存的资源可以满足它的最大需求量则按当前的申请量分配资源,否则就推迟分配。当进程在执行中继续申请资源时,先测试该进程本次申请的资源数是否超过了该资源所剩余的总量。若超过则拒绝分配资源,若能满足则按当前的申请量分配资源,否则也要推迟分配。
算法实现:
初始化
由用户输入数据,分别对可利用资源向量矩阵AVAILABLE、分配矩ALLOCATION、需求矩阵NEED赋值。
具体过程:
银行家算法的基本思想是分配资源之前,判断系统是否是安全的;若是,才分配。它是最具有代表性的避免死锁的算法。在该方法中把系统的状态分为安全状态和不安全状态,只要能使系统始终都处于安全状态,便可以避免发生死锁。
设进程q提出请求REQUEST [i],则银行家算法按如下规则进行判断。
(1)如果REQUEST [q] [i]<= NEED[q][i],则转(2);否则,出错。
(2)如果REQUEST [q] [i]<= AVAILABLE[i],则转(3);否则,等待。
(3)系统试探分配资源,修改相关数据:
AVAILABLE[i]-=REQUEST[q][i];
ALLOCATION[q][i]+=REQUEST[q][i];
NEED[q][i]-=REQUEST[q][i];
(4)系统执行安全性检查,如安全,则分配成立;否则试探险性分配作废,系统恢复原状,进程等待。
安全性检查算法
(1)设置两个工作向量Work=AVAILABLE;FINISH
(2)从进程集合中找到一个满足下述条件的进程,
FINISH==false;
NEED<=Work;
如找到,执行(3);否则,执行(4)
(3)设进程获得资源,可顺利执行,直至完成,从而释放资源。
Work=Work+ALLOCATION;
Finish=true;
GOTO 2
(4)如所有的进程Finish= true,则表示安全;否则系统不安全。
寻找安全序列时需要注意的问题:
在安全检查中寻找符合条件的进程时,不能仅用一层循环来实现对所有进程的遍历,否则无法过滤错误的序列。
应该采用类似与DFS的方式遍历每一种可能的序列,不断的”试错“,找到错误的序列就主动回退寻找下一条序列。
具体方法:
-
每次从tem+1位置开始遍历,判断是否有符合条件(work<need[p]且Finish[j] == 0)的进程p
-
如果有符合的进程就执行:
- Finish[p] = 1;
- Cstack.push§;
- Work[i] += Alloc[flag0][i] //进程释放资源
- tem = 0;//有元素入栈后 tem=1 下次从头开始扫描
- 如果没有符合的进程并且进程已经全部进入安全序列则:
- return 1;//如果已经全部进入安全序列 返回1
- 返回对符合条件进程的判断步骤
- 如果没有符合的进程并且进程没有全部进入安全序列并且栈不空则:
- tem = Cstack.top();
- Finish[tem] = 0;
- Work[i] -= Alloc[tem][i];//回归进程释放资源
- Cstack.pop();
- 返回对符合条件进程的判断步骤
- 如果没有符合的进程并且进程没有全部进入安全序列并且栈为空则:
- return 0;
银行家算法的C++实现:
#include <iostream>
#include <stack>
using namespace std;
#define n 5//进程个数
#define m 3//资源种类
bool Finish[n+1];//安全序列
stack<int> Cstack;
//int Available[m + 1], Alloc[n + 1][m + 1], Need[n + 1][m + 1];
int Available[m+1] = { 0,2,3,3 };
int Alloc[n+1][m+1] = {0,0,0,0, 0,2,1,2, 0,4,0,2, 0,3,0,5, 0,2,0,4, 0,3,1,4 };
int Need[n+1][m+1] = { 0,0,0,0, 0,3,4,7, 0,1,3,4, 0,0,0,3, 0, 2,2,1, 0,1,1,0};
/*几组测试数据
2 0 3 4 无法满足,因为请求资源大于系统现有的资源数
4 1 0 1 4 2 3 5 1
1 2 0 1 安全检查无法满足,该进程被阻塞:
3 0 0 2 3 2 4 5 1 */
void StackPrint(stack<int> &a) { // 用于递归的输出一个栈的信息
if (a.empty())
return;
int A = a.top();
a.pop();
StackPrint(a);
cout << A << "-->";
}
int SafeCheck() {
for (int i = 1; i <= n; i++)
Finish[i] = 0;//安全序列,0表示i号进程不在其中
int Work[m+1];
for (int i = 1; i <= m; i++)
Work[i] = Available[i];
int tem = 0;
while (1) {
int flag0 = 0;//是否存在
int flag1;//该进程work可满足(work<need[p])的p
for (int j = tem+1; j <= n; j++) {
flag1 = 1;
for (int i = 1; i <= m; i++)
if (Need[j][i] > Work[i])
flag1 = 0;
if (Finish[j] == 0 && flag1 == 1) {
flag0 = j;
break;
}
}
if (flag0 != 0) {
Finish[flag0] = 1;//进程进入安全序列
Cstack.push(flag0);
tem = 0;//有元素入栈后 tem=1 下次从头开始扫描
for (int i = 1; i <= m; i++)
Work[i] += Alloc[flag0][i];//进程释放资源
}
else {
if (Cstack.size() == n)
return 1;//如果已经全部进入安全序列 返回1
if (!Cstack.empty()) {
tem = Cstack.top();
Finish[tem] = 0;
for (int i = 1; i <= m; i++)
Work[i] -= Alloc[tem][i];//回归进程释放资源
Cstack.pop();
}
else
return 0;
}
}// while结束
}
int main()
{
while (1) {
int Request[m + 1];//当前的请求
int p;//当前是几号进程在请求资源;
cout << "请输入当前请求资源的进程编号(-1可退出): ";
cin >> p;
if (p > n) {
cout << "输入错误" << endl;
continue;
}
if (p == -1) return 0;//输入-1 表示退出程序
cout << "请输入当前进程请求资源的向量: ";
for (int i = 1; i <= m; i++)
cin >> Request[i];
int flag = 1;
for (int i = 1; i <= m; i++) //检查Request < Need 即请求是否合法(小于宣称的最大值)
if (Request[i] > Need[p][i])
flag = 0;//不合法
if (flag == 0) {
cout << "不合法,因为请求资源大于宣称的最大资源" << endl;
continue;
}
for (int i = 1; i <= m; i++) //检查Request[i] < Available[i] 即系统资源够不够当前用
if (Request[i] > Available[i])
flag = 0;//无法满足
if (flag == 0) {
cout << "无法满足,因为请求资源大于系统现有的资源数" << endl;
continue;
}
//程序执行到此表示已经通过上述安全检查,可以试探性的把资源分配给该进程:
for (int i = 1; i <= m; i++) {
Available[i] -= Request[i];
Alloc[p][i] += Request[i];
Need[p][i] -= Request[i];
}
//分配资源后,执行安全性算法检查
if (SafeCheck()) {
cout << "安全检查可以满足,安全序列为:" << endl;
StackPrint(Cstack);
}
else
cout << "安全检查无法满足,该进程被阻塞:" << endl;
for (int i = 1; i <= m; i++) {
Available[i] += Request[i];
Alloc[p][i] -= Request[i];
Need[p][i] += Request[i];
}
}//主循环
}
运行结果:
根据题目要求,输入四组测试数据进行测试,得到的结果如下图:
测试数据及结果整理到表格内可得:
编号 | 进程号 | 申请的资源 | 输出结果: |
---|---|---|---|
1 | 2 | 0,3,4 | 无法满足,因为请求资源大于系统现有的资源数 |
2 | 4 | 1,0,1 | 4 2 3 5 1 |
3 | 1 | 2,0,1 | 安全检查无法满足,该进程被阻塞: |
4 | 3 | 0,0,2 | 3 2 4 5 1 |