Bootstrap

银行家算法c++

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;
	}

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;