操作系统: 避免死锁
文章目录
一、操作目的
死锁会引起计算机工作僵死,因此操作系统中必须防止。本实验的目的在于使用高级语言编写和调试一个系统动态分配资源的简单模拟程序,了解死锁产生的条件和原因,并采用银行家算法有效地防止死锁的发生,以加深对知识的理解。
二、主要内容
设计有 n 个进程共享m 个系统资源的系统,基于银行家算法和安全性算法,进程可动态的申请和释放资源,系统按各进程的申请动态的分配资源。
三、需求分析
资源数、资源分配状况全随机生成。注意资源初始分配随机生成的合理性。
实现安全序列的全求解。
实现图形化演示。
1、产生的各种随机数的取值范围加以限制,如所需的各类资源数限制在 1~20之间。
2、进程数 n 不要太大通常取 4~8 个
3、使用动态数据结构
5、若有可能请在图形方式下,将 PCB 的调度用图形成动画显示。
四、算法过程
首先实现的主体是一个进程类,而这个进程类的成员属性又包含了Max
,Allocation
,Need
三个容器,其次还有name和判断是否完成的其他成员属性。而这些类又全部存放在一个类容器中。
1、银行家算法:
整体思想就是遍历整个进程容器,如果有未完成的进程且满足安全可分配(Need <= Available
)则分配出自己的资源(Allocation
)进行后面的遍历,如果遍历完一遍发现没有可执行的程序了,说明要么所有的进程都被分配了进程,或者有进程无法分配资源导致这是一个不安全的序列,正常输出安全序列或者不安全说明即可。
而对于图形化演示的过程思路也很简单,就是在这个算法的基础上实现出每次对比的过程,包括:每个未完成进程的Need和Available资源的逐个对比,安全分配则标记成绿色,否则标记成红色,再用表格的形式统一展示。
2、找到所有安全性序列:
用到深度优先搜素+递归的方法,具体的逻辑就是先遍历找到一个安全的进程,递归调用这个函数,再到找到这个进程释放资源的基础上来再次遍历寻找进程,而找到最后一个进程了以后就会回溯回来再上一个进程的基础上继续寻找。最后我们将所有的安全序列存放在一个安全序列容器中,输出即可。
3、流程图
五、算法过程中问题改进和不足
1、遇到的问题及解决
问题 1:
首先想法比较简单,就是类,每个对象三个状态,一起为一个类数组或者容器,循环判断每次的资源对比,然后处理资源。
问题就是:
- 随机数的范围数值怎么确定?怎么安排才合理?
- 怎么实现随机数生成多个数据,并且和为初始的资源数量?
- 图像怎么表示传达?不能单纯的表格形式。
- 怎么找到所有的解?
解决 1:
-
依据实际需求:对于进程的已有资源
Allocation
,把范围设定为 0 - 3 是合理的,通常初始分配的资源不会太多。而最大资源Process_Max
要比Allocation
大,且最多为 9,这限制了进程的最大资源需求。 -
避免极端情况:随机数范围的设置要避免出现极端情况,例如资源数量为 0 或者过大。试过将初始的
Available
从随机成3-5,但是生成的安全序列的占比太高,所以进行调整生成 2-4初始可分配的资源,这样可以提高不安全序列的占比。最终测试23次,其中有安全性序列的次数为10次,占比43.5%。 -
将银行家算法的比较过程将比较的过程加以颜色,满足为绿色,不满足为红色,加上 Windows 控制台 API 和
Sleep()
停顿时间动画输出。 -
所有解,暂时就想到了深度优先遍历加上递归是比较好的方式。
问题2:
在找到所有安全序列的算法中,开始的时候并没有在创建在这个状态下的临时副本,导致用作业测试的时候结果并不正确。
解决2:
如果不创建新的临时副本状态,那么可能会导致安全序列的丢失和污染数据源,比如:假设进程 P0 和 P1 都能在当前 Available 下运行:先选择 P0 并递归搜索后续序列时,会修改全局状态(如
Available += P0.Allocation
)。当回溯到父节点尝试选择 P1
Available
已被 P0 的修改污染,导致 P1 的检查条件错误(可能误判为不可分配)。而且可能会发生死循环的风险:因为多个分支反复修改同一份
finished 数组
,可能导致某个进程被错误标记为“已完成”,而实际未分配资源,进而引发无限递归或非法访问。
2、依然存在的不足:
-
首先就是图形化演示的问题,虽然满足了动态演示的过程,但是还是建立在一维之上,用数字的大小来比较,可能不够具体和形象化,所以可以改进成二维的方式,比如柱形图,扇形图的形式。
-
其次就是找到所有安全序列的问题,因为我进程的数量是4-6个,如果是生成7-8个进程,那么安全系列按照阶乘相乘的话可能会非常多,在每次的递归状态下都新建进程副本,可用资源副本,和标记完成进程的副本,三个容器副本,在进程数量较多的情况下产生的空间会非常多,那么解决方案我暂时想到的就是限界剪枝法,限定出上下界,防止不必要的递归,但没有具体的实现出来。
六、附录
核心代码
银行家算法:
// 银行家算法
void Banker_Algorithm(vector<Process>& vP, vector<int> Avai) {
bool find = false;
int safecount = 0;
string safeList = "";
do {
find = false;
for (int i = 0; i < vP.size(); ++i) {
if (!vP[i].Finish) {
bool canAllocate = true;
// 显示当前检查的进程
// system("cls");
// 打印表格,高亮当前进程的Need列
printForm(vP, Avai, i);
cout << "正在检查进程 " << vP[i].name << "..." << endl;
cout << "详细比较过程:" << endl;
// 逐个比较资源
for (int j = 0; j < Avai.size(); ++j) {
char Resources_name = static_cast<char>('A' + j);
// 显示比较的资源索引
cout << "资源" << Resources_name << ": ";
// 显示Need值
setConsoleColor(Blue);
cout << "Need[" << Resources_name << "] = " << vP[i].Need[j];
resetConsoleColor();
cout << " 小于等于? ";
// 显示Available值
setConsoleColor(Yellow);
cout << "Available[" << Resources_name << "] = " << Avai[j];
resetConsoleColor();
cout << " - ";
// 比较并显示结果
if (vP[i].Need[j] <= Avai[j]) {
setConsoleColor(Green);
cout << " Yes (足够)";
resetConsoleColor();
cout << endl;
}
else {
setConsoleColor(Red);
cout << " NO (不足)";
resetConsoleColor();
canAllocate = false;
}
// 如果发现不足,立即停止比较该进程的其他资源
if (!canAllocate) {
setConsoleColor(Red);
cout << " 因为进程 " << vP[i].name << " 无法分配,因为资源 " << Resources_name << " 不足" << endl;
resetConsoleColor();
break;
}
// 暂停一下让用户看清比较过程
Sleep(1500);
}
// 如果所有资源都足够
if (canAllocate) {
vP[i].Finish = true;
// 详细查看附录
addTowVector(Avai, vP[i].Allocation);
find = true;
safecount++;
safeList += vP[i].name;
if (safecount != vP.size()) {
safeList += " -> ";
}
// 显示分配后的状态
// system("cls");
setConsoleColor(BrightGreen);
cout << "进程 " << vP[i].name << " 可以分配资源!" << endl;
cout << "进程 " << vP[i].name << " 将已有资源 Allocation ";
outputNeed(vP[i]);
cout << " 分配给 Allocation --> ";
outputAvailable(Avai);
cout << endl;
resetConsoleColor();
printForm(vP, Avai);
Sleep(2000);
}
else { // 有资源不足的情况,这个进程无法分配资源
// 暂停一下让用户看清比较结果
Sleep(2000);
}
}
}
} while (find); // 只要在这次循环中有进程被分配资源,就继续循环
// 显示最终结果
// system("cls");
if (safecount == vP.size()) {
setConsoleColor(BrightGreen);
cout << "找到安全序列: " << safeList << endl;
}
else {
setConsoleColor(Red);
cout << "无安全序列" << endl;
}
resetConsoleColor();
printForm(vP, Avai);
}
查找所有安全序列:
// 查找所有安全序列
/*
算法思路: ***** 深度优先搜索 *****
首先 我们在找到第一个可以分配资源的进程基础上,释放出进程持有的资源,更新 Available 再对后序的进程进行递归遍历
其次对每找到一个安全序列我们就储存在一个 allSafeSequences 容器中。
最后我们只需要判断这个 allSafeSequences 容器是否为空,即可确定有无安全序列。
*/
// 查找所有安全序列的辅助函数
void findAllSafeSequences_help(vector<Process>& vP, vector<int> Available,
vector<string>& allSafeSequences, string currentSequence, vector<bool>& finished) {
bool find = false;
for (int i = 0; i < vP.size(); ++i) {
// 进程未完成且现有的资源大于进程所需要的资源
// compareTwoVector()函数详细在附录
if (!finished[i] && compareTwoVector(Available, vP[i].Need)) {
// 创建一个新的状态副本,以免修改原状态
vector<Process> newVP = vP;
vector<int> newAvai = Available;
vector<bool> newFinished = finished;
// 标记进程为已完成
newFinished[i] = true;
newVP[i].Finish = true;
// 释放资源
addTowVector(newAvai, newVP[i].Allocation);
// 更新当前序列,为空则添加安全序列的第一个进程名称,不为空则向后添加分配资源的进程名称
string newSequence = currentSequence.empty() ? newVP[i].name
: currentSequence + " -> " + newVP[i].name;
// 递归查找
findAllSafeSequences_help(newVP, newAvai, allSafeSequences, newSequence, newFinished);
find = true;
}
}
// 如果没有找到任何可执行的进程,检查是否所有进程都完成了
if (!find) {
bool allFinished = true;
for (auto it = finished.begin();it != finished.end();++it) {
if (!(*it)) { // 说明还有进程没被分配资源,这不是一个安全序列
allFinished = false;
break;
}
}
// 其实因为已经初始化 finished 中所有的元素都为 false ,但是以防御性编程的角度,我们还是加上 !currentSequence.empty()
if (allFinished && !currentSequence.empty()) {
allSafeSequences.push_back(currentSequence);
}
}
// 否则说明有可执行的进程,继续遍历
}
// 查找所有安全序列的入口函数
void findAllSafeSequences(vector<Process> vP, vector<int> Available) {
vector<string> allSafeSequences; // 储存找到的所有安全序列
vector<bool> finished(vP.size(), false); // 标记哪些进程已经完成,初始为均未完成
// 调用辅助函数
findAllSafeSequences_help(vP, Available, allSafeSequences, "", finished);
if (allSafeSequences.empty()) {
cout << "无安全序列" << endl;
}
else {
cout << "共找到 " << allSafeSequences.size() << " 个安全序列:" << endl;
for (const string& sequence : allSafeSequences) {
setConsoleColor(BrightGreen); // 高亮颜色
cout << "安全序列: " << sequence << endl;
}
resetConsoleColor();
}
}
结果展示
(所有分析生成完以后的总结果)
视频结果
(因为图片放过来不清楚,我们添加视频解释)
操作系统银行家算法结果运行
七、温馨说明
学力有限,拙文恐有未周,敬祈读者批评指正,由甚感激!若有讹误或排版不当,尚祈见谅!
☺️☺️☺️☺️☺️