银行家算法
1. 银行家算法
1.1 银行算法家的目的
银行家算法是避免死锁的一种重要方法, 能够有效的在资源分配的过程中, 对系统的安全性进行检测。通过银行家算法设计与实现, 可以加深对死锁的理解, 掌握死锁的预防、 避免、 检测和解除的基本原理, 重点掌握死锁的避免方法—银行家算法。 初步具有研究、 设计、 编制和调试操作系统模块的能力。
1.2 银行算法家的作用
银行家算法通过在进程请求资源时进行安全性检查,来确保系统不会进入死锁状态。在银行家算法中,客户申请贷款的数量是有限的,每个客户在第一次申请贷款时要声明完成该项目所需的最大资金量,在满足所有贷款要求时,客户应及时归还。如果所有进程都可以完成并终止,则一个状态被认为是安全的。银行家算法的主要作用是避免死锁的产生,特别是在进程间共享资源时。
2. 设计原理
2.1 银行家算法的数据结构
● 可用资源向量 Available [m]
m为系统中资源种类数, Available[j]=k表示系统中第j类资源数为k个。
● 最大需求矩阵Max[n][m]
n为系统中进程数, Max[i][j]=k表示进程i对j类资源的最大需求数为k。
● 分配矩阵Allocation[n][m]
Allocation[i][j]=k表示进程i已分得j类资源的数目为k个。
● 需求矩阵Need[n][m]
Need[i][j]=k 表示进程i还需要j类资源k个。
Need[i][j]=Max[i][j]-Allocation[i][j]
2.2 银行家算法介绍
假设在进程并发执行时进程i提出请求j类资源k个后, 表示为Requesti[j]=k,系统按下述步骤进行安全检查:
1.如果Requesti≤Needi则继续以下检查, 否则显示需求申请超出最大需求值的错误。
2. 如果Requesti≤Available则继续以下检查, 否则显示系统无足够资源, Pi阻塞等待。
3.系统试探把要求的资源分配给进程i并修改有关数据结构的值:
Available = Available-Requesti ;
Allocationi =Allocationi+Requesti ;
Needi=Needi-Requesti ;
4.系统执行安全性算法, 检查此次资源分配后, 系统是否处于安全状态, 若安全,才正式将资源分配给进程i, 以完成本次分配; 否则将试探分配作废, 恢复原来的资源分配状态, 让进程Pi等待。
2.3 安全性算法
A.设置完成标志向量 Finish[n]。
初始化: Finish[i] = false
表示i进程尚末完成
B.从进程集合n设置Work[m]表示系统可提供给进程的各类资源数目。 初始化:Work = Available
,
中找到一个能满足下述二个条件:Finish[i] = false
, Needi≤Work,如找到则执行步骤C, 找不到则执行步骤D
C.当进程i获得资源后可顺利执行直到完成, 并释放出分配给它的资源, 表示如下:
work = work+Allocationi ; Finish[i]=true
;
转执行步骤B。
D.如果所有的Finish[i]=ture
, 则表示系统处于安全状态, 否则系统处于不安全状态。
3. 实验要求
能够考虑社会、 健康、 安全、 法律、 文化及环境等因素的影响, 针对银行家算法避免死锁进行建模, 设计实验方案, 运用恰当的集成开发工具编程模拟实现上述算法, 要求:
(1) 有录入界面, 动态录入进程个数、 资源种类数、 诸进程对各类资源的最大需求、 T0时刻系统为诸进程已分配的资源数以及系统中各类资源的资源总数;
(2) 能够判断T0时刻系统是否安全, 进程之间能否无死锁的运行下去, 若能输出安全序列;
(3) 有输出界面, 能够从录入界面模拟进程又提出新的申请, 根据银行家算法判断请求是否能得到满足, 并输出当前时刻下诸进程对各类资源的需求列表及系统可用资源数, 若能输出安全序列, 若不能分配输出无法分配的原因。
4. 银行家算法实例
例题就用课本上的例题,题目如下图所示:
假定系统中有5个进程{P0,P1,P2,P3,P4},和3类资源{A,B,C},各类资源的数量分别为10、5、7,在时刻的资源分配情况如图3-17所示。
Max矩阵
:一个m×n的矩阵,其中m表示进程数,n表示资源数。Max[i][j]表示进程i需要资源j的最大数量。
Allocation矩阵
:一个m×n的矩阵,其中m表示进程数,n表示资源数。Allocation[i][j]表示进程i已经分配了资源j的数量。
Need矩阵
:一个m×n的矩阵,其中m表示进程数,n表示资源数。Need[i][j]表示进程i还需要资源j的数量。
Available矩阵
:一个长度为n的一维矩阵,表示当前系统中还有多少可用资源。其中,Max矩阵和Allocation矩阵的差值就是Need矩阵,即Need[i][j] = Max[i][j] - Allocation[i][j]。Available矩阵表示当前系统中还有多少可用资源,可以通过计算Allocation矩阵的列和再与Max矩阵的列相减得到。
通过上述概念就可以分析这些进程在t0时刻是否安全,算法过程
如下:用可用的资源(Available)去和进程还需要的资源(Need)进行比较,如果1.Available>=Need,则说明该进程可以正常运行至结束,该进程结束后,已分配的资源得到释放(Allocation),所以当前可用的资源变为Available+Allocation;如果2.Available<Need,则说明当前进程不能正常进行,换另一个进程重复比较,如果到最后还有进程未运行完毕,则说明进程产生死锁,当前系统不安全。
如本例题的运算结果如下图所示:
本题用Banker类来实现,由题易知需要一些矩阵来进行模拟进程资源,如Max矩阵,Allocation矩阵,Need矩阵,Available矩阵,这里做些处理,用Work矩阵来表示可用资源的数量(即上图中Work+Allocation,而上图中的Work矩阵可以去掉),再用一个Finish矩阵表示进程是否成功运行。还需要一个安全序列,如上图中{P1,P3,P4,P2,P0},所以可以用一个哈希表(set)来存某进程是否运行结束(set主要用来查找在不在)。另外还可以加上线程的数目(_m)和可用于资源的种类(_n)。如下为类中的成员变量:
class Banker
{
private:
vector<bool> Finish; //进程完成标志向量
vector<vector<int>> Max; //最大需求矩阵
vector<vector<int>> Allocation; //已分配矩阵
vector<vector<int>> Need; //需求矩阵
vector<vector<int>> Work; //可用资源
vector<int> Available; //可用资源向量
unordered_set<int> set; //进程完成的安全序列
int _m; //进程数目
int _n; //资源的种类
};
该类的构造函数用来初始化数据,因为没有开辟空间(使用new,malloc,realloc,calloc等函数),所以析构函数不用写,构造析构函数如下:
class Banker
{
public:
Banker(const int& m, const int& n)
:Finish(m, false), Max(m, vector<int>(n)), Allocation(m, vector<int>(n)), Need(m, vector<int>(n)), Work(m, vector<int>(n)),
Available(n), _m(m), _n(n)
{
cout << "请输入最大需求矩阵:" << endl;
for (int i = 0; i < m; ++i)
{
for (int j = 0; j < n; ++j)
{
cin >> Max[i][j];
}
}
cout << "请输入已分配资源的矩阵:" << endl;
for (int i = 0; i < m; ++i)
{
for (int j = 0; j < n; ++j)
{
cin >> Allocation[i][j];
}
}
for (int i = 0; i < m; ++i)
{
for (int j = 0; j < n; ++j)
{
Need[i][j] = Max[i][j] - Allocation[i][j];
}
}
cout << "请输入可用资源数目:" << endl;
for (int i = 0; i < n; ++i)
{
cin >> Available[i];
}
}
~Banker()
{}
};
接下来就是判断t0时刻系统是否安全,算法思路如上述所示,具体代码和详细解释(checkSafe),在这里设计的是如果系统不安全,则强制退出,代码如下:
bool compare(const vector<int>& v1, const vector<int>& v2)
{
for (int i = 0; i < _n; ++i)
{
if (v1[i] < v2[i])
{
return false;
}
}
return true;
}
void checkSafe()
{
int flag = 1; //判断进程是否全部可以执行完毕
int prev = 0; //单独执行Work
Work[0] = Available; //更新Work,Work矩阵在每次检查系统安全是需要修改的
while (flag) //遍历进程可能遍历多次,并且可能产生死锁,flag是每次遍历进程是否有进程成功运行的标志
{
flag = 0;
//遍历每一个进程
for (int i = 0; i < _m; ++i)
{
unordered_set<int>::iterator it = set.find(i);
if (it != set.end())
continue;
if (compare(Work[prev], Need[i])) // 如果可用资源大于等于还需要的资源数量,则该进程正常运行
{
set.insert(i);
Finish[i] = true;
flag = 1;
for (int j = 0; j < _n; ++j)
{
Work[i][j] = Allocation[i][j] + Work[prev][j];
}
//更新Work的prev
prev = i;
}
}
}
if (set.size() != _m) //如果运行结束的所有进程的数量不等于原有进程的数量,则证明死锁
{
cout << "进程没有全部执行完毕,已产生死锁,此时系统是不安全的。" << endl;
cout << "已强制退出" << endl;
exit(1); //退出码设为1
}
}
如上就可以检查系统是否安全。还有一个问题是进程发送请求向量,如下图所示:
这道题要输入Request矩阵,而且可以是多次输入Request,具体代码和详细解释(checkSafe)如下:
void request()
{
int m = 0;
vector<int> Request(_n);
cout << "请输入需要请求资源的进程:";
cin >> m;
cout << "请输入P" << m << "发出请求向量Request:";
for (int i = 0; i < _n; ++i)
cin >> Request[i];
cout << "系统按银行家算法进行检查。" << endl;
int flag = 1; //是否可以进行系统检查的标志
if (compare(Need[m], Request))
cout << "Request<=Need( ";
else
{
cout << "Request>Need( ";
flag = 0;
}
for (auto& x : Need[m])
cout << x << ' ';
cout << ")" << endl;
if (compare(Available, Request))
cout << "Request<=Available( ";
else
{
cout << "Request>Available( ";
flag = 0;
}
for (auto& x : Available)
cout << x << ' ';
cout << ")";
if (flag)
{
//更新数据
for (int i = 0; i < _n; ++i)
Need[m][i] -= Request[i];
for (int i = 0; i < _n; ++i)
Available[i] -= Request[i];
for (int i = 0; i < _n; ++i)
Allocation[m][i] += Request[i];
//发送请求后,再检查进程安全之前切记将set表释放,重新统计新的安全序列
set.erase(set.begin(), set.end());
//或者set.clear();
//每次发送请求后进行检查系统安全并打印结果
checkSafe();
print();
}
else
{
cout << "让P" << m << "等待" << endl;
cout << endl;
}
}
打印函数如下:
void print()
{
cout << "Available:";
for (int i = 0; i < _n; ++i)
cout << Available[i] << ' ';
cout << endl;
cout << " 资源 | Need | Allocation | Work+Allocation | Finish " << endl; //本题默认是5个进程3个资源,如果数量不同打印结果可能不美观
for (auto i : set) //用进程完成的安全序列来遍历所有数据
{
cout << " P" << i << " |";
for (int j = 0; j < _n; ++j)
{
cout << Need[i][j] << " ";
}
cout << "| ";
for (int j = 0; j < _n; ++j)
{
cout << " " << Allocation[i][j] << " ";
}
cout << " |";
for (int j = 0; j < _n; ++j)
{
cout << " " << Work[i][j] << " ";
}
cout << " |" << Finish[i] << endl;;
}
cout << "利用安全性算法进行分析,可知存在着一个安全序列";
for (auto it : set)
{
cout << "P" << it << ",";
}
cout << "故系统是安全的" << endl;
cout << endl;
}
5. 完整代码和运行测试
5.1 测试结果
第一问:
第二问:
第三问:
第四问:
5.2 完整代码
头文件banker.h代码如下:
#pragma once
#include <iostream>
#include <vector>
#include <unordered_set>
using namespace std;
class Banker
{
public:
Banker(const int& m, const int& n)
:Finish(m, false), Max(m, vector<int>(n)), Allocation(m, vector<int>(n)), Need(m, vector<int>(n)), Work(m, vector<int>(n)),
Available(n), _m(m), _n(n)
{
cout << "请输入最大需求矩阵:" << endl;
for (int i = 0; i < m; ++i)
{
for (int j = 0; j < n; ++j)
{
cin >> Max[i][j];
}
}
cout << "请输入已分配资源的矩阵:" << endl;
for (int i = 0; i < m; ++i)
{
for (int j = 0; j < n; ++j)
{
cin >> Allocation[i][j];
}
}
for (int i = 0; i < m; ++i)
{
for (int j = 0; j < n; ++j)
{
Need[i][j] = Max[i][j] - Allocation[i][j];
}
}
cout << "请输入可用资源数目:" << endl;
for (int i = 0; i < n; ++i)
{
cin >> Available[i];
}
}
bool compare(const vector<int>& v1, const vector<int>& v2)
{
for (int i = 0; i < _n; ++i)
{
if (v1[i] < v2[i])
{
return false;
}
}
return true;
}
void checkSafe()
{
int flag = 1; //判断进程是否全部可以执行完毕
int prev = 0; //单独执行Work
Work[0] = Available; //更新Work
while (flag) //遍历进程可能遍历多次,并且可能产生死锁,flag是每次遍历进程是否有进程成功运行的标志
{
flag = 0;
//遍历每一个进程
for (int i = 0; i < _m; ++i)
{
unordered_set<int>::iterator it = set.find(i);
if (it != set.end())
continue;
if (compare(Work[prev], Need[i])) // 如果可用资源大于等于还需要的资源数量,则该进程正常运行
{
set.insert(i);
Finish[i] = true;
flag = 1;
for (int j = 0; j < _n; ++j)
{
Work[i][j] = Allocation[i][j] + Work[prev][j];
}
//更新Work的prev
prev = i;
}
}
}
if (set.size() != _m) //如果运行结束的所有进程的数量不等于原有进程的数量,则证明死锁
{
cout << "进程没有全部执行完毕,已产生死锁,此时系统是不安全的。" << endl;
cout << "已强制退出" << endl;
exit(1); //退出码设为1
}
}
void request()
{
int m = 0;
vector<int> Request(_n);
cout << "请输入需要请求资源的进程:";
cin >> m;
cout << "请输入P" << m << "发出请求向量Request:";
for (int i = 0; i < _n; ++i)
cin >> Request[i];
cout << "系统按银行家算法进行检查。" << endl;
int flag = 1;
if (compare(Need[m], Request))
cout << "Request<=Need( ";
else
{
cout << "Request>Need( ";
flag = 0;
}
for (auto& x : Need[m])
cout << x << ' ';
cout << ")" << endl;
if (compare(Available, Request))
cout << "Request<=Available( ";
else
{
cout << "Request>Available( ";
flag = 0;
}
for (auto& x : Available)
cout << x << ' ';
cout << ")";
if (flag)
{
//更新数据
for (int i = 0; i < _n; ++i)
Need[m][i] -= Request[i];
for (int i = 0; i < _n; ++i)
Available[i] -= Request[i];
for (int i = 0; i < _n; ++i)
Allocation[m][i] += Request[i];
//发送请求后,再检查进程安全切记将set表释放,重新统计新的安全序列
set.erase(set.begin(), set.end());
//或者set.clear();
checkSafe();
print();
}
else
{
cout << "让P" << m << "等待" << endl;
cout << endl;
}
}
void print()
{
cout << "Available:";
for (int i = 0; i < _n; ++i)
cout << Available[i] << ' ';
cout << endl;
cout << " 资源 | Need | Allocation | Work+Allocation | Finish " << endl;
for (auto i : set)
{
cout << " P" << i << " |";
for (int j = 0; j < _n; ++j)
{
cout << Need[i][j] << " ";
}
cout << "| ";
for (int j = 0; j < _n; ++j)
{
cout << " " << Allocation[i][j] << " ";
}
cout << " |";
for (int j = 0; j < _n; ++j)
{
cout << " " << Work[i][j] << " ";
}
cout << " |" << Finish[i] << endl;;
}
cout << "利用安全性算法对t0时刻进行分析,可知在t0时刻存在着一个安全序列";
for (auto it : set)
{
cout << "P" << it << ",";
}
cout << "故系统是安全的" << endl;
cout << endl;
}
~Banker()
{}
private:
vector<bool> Finish; //进程完成标志向量
vector<vector<int>> Max; //最大需求矩阵
vector<vector<int>> Allocation; //已分配矩阵
vector<vector<int>> Need; //需求矩阵
vector<vector<int>> Work; //可用资源
vector<int> Available; //可用资源向量
unordered_set<int> set; //进程完成的安全序列
int _m; //进程数目
int _n; //资源的种类
};
main.cc文件代码如下:
#include "banker.h"
int main()
{
int m = 0, n = 0; //p表示进程的数目,n表示资源的种类
cout << "请输入进程的数目:";
cin >> m;
cout << "请输入资源的种类:";
cin >> n;
Banker bank(m, n);
bank.checkSafe();
bank.print();
while (true)
{
bank.request();
}
return 0;
}
//测试数据
//7 5 3
//3 2 2
//9 0 2
//2 2 2
//4 3 3
//0 1 0
//2 0 0
//3 0 2
//2 1 1
//0 0 2
//3 3 2
在最后,有些老师会在意输入的一些小问题,如上代码中,没有输入总的资源数目,而输入的是分配之后剩余的资源数目(Available)。如果强调输入的是总的资源数目,剩余的资源数目(Available)是计算出来的,那么在定义一个类的私有成员vector<int> Total,表示总的资源数目,然后将类的构造函数进行修改即可,修改后的析构函数代码如下:
Banker(const int& m, const int& n)
:Finish(m, false), Max(m, vector<int>(n)), Allocation(m, vector<int>(n)), Need(m, vector<int>(n)), Work(m, vector<int>(n)),
Available(n), _m(m), _n(n), Total(n)
{
cout << "请输入最大需求矩阵:" << endl;
for (int i = 0; i < m; ++i)
{
for (int j = 0; j < n; ++j)
{
cin >> Max[i][j];
}
}
cout << "请输入已分配资源的矩阵:" << endl;
for (int i = 0; i < m; ++i)
{
for (int j = 0; j < n; ++j)
{
cin >> Allocation[i][j];
}
}
for (int i = 0; i < m; ++i)
{
for (int j = 0; j < n; ++j)
{
Need[i][j] = Max[i][j] - Allocation[i][j];
}
}
//cout << "请输入可用资源数目:" << endl;
//for (int i = 0; i < n; ++i)
//{
// cin >> Available[i];
//}
cout << "请输入总的资源数目:" << endl;
for (int i = 0; i < n; ++i)
{
cin >> Total[i];
}
//计算Available
vector<int> temp = Total; //尽量不要修改Total数组
for (int i = 0; i < m; ++i)
{
for (int j = 0; j < n; ++j)
{
temp[j] -= Allocation[i][j];
}
}
Available = temp;
}