多道程序设计中,经常是若干个进程同时处于就绪状态,必须依照某种策略来决定那个进程优先占有处理机。因而引起进程调度。本代码模拟在单处理机情况下的处理机调度问题。
将现实问题经过几次抽象(细化)处理,最后到求解域中只是一些简单的算法描述和算法实现问题。即将系统功能按层次进行分解,每一层不断将功能细化,到最后一层都是功能单一、简单易实现的模块。求解过程可以划分为若干个阶段,在不同阶段采用不同的工具来描述问题。在每个阶段有不同的规则和标准,产生出不同阶段的文档资料。——百度百科
这个方法特别感谢我的老师推荐给平常写代码比较少的我,对代码的实现和学习都有很大的帮助,同时也会大大减少代码报错的可能性,让排查错误也更加简单,也能提高自己写代码的自信,很推荐同样对代码不熟悉的朋友使用。
#include
#include
#include
由于要使用字符串定义进程状态(就绪、执行、完成)、同时还需要兼顾用户使用的可观方便性,输入字符串(比如:Yes or No)会比输入数字(比如:1、2、3)更容易理解和操作,所以引入头文件string。
我们还需使用map模板来实现进程的按优先级排序以及利用map自动按key升序排序,所以可以将key定义为优先级,这样可以简化甚至直接免除了我们排序的算法。
对于逐步求精后代码功能逐渐完善,我们需要用到随机数给进程随机出需要运行时间和优先级,所以添加宏定义,方便后续代码随机数的使用,也提高代码的可读性。
cout << "请输入进程数n(建议4~8个):" << endl;
int n;
cin >> n;
cout << "请选择调度方法:1.动态优先权法请输入Y或y 2.轮转法请输入N或n" << endl;
string cho;
cin >> cho;
int id;//进程编号
if (cho == "Y" || cho == "y"){
}
else if (cho == "N" || cho == "n") {
}
else { cout << "您的输入有误!请输入Y / y或N / n" << endl; }
}
在实验中老师推荐进程数最好在4~8个之间,原因应该是能检测代码编写的正确性的同时可以高效地运行,所以我们这里用整型n定义进程数,并且输出一行语句提示用户输入合理范围的进程数。
再定义字符串类型的变量cho(这里是choose的缩写,可以增强代码的可视性,直接定义成choose或者其他变量均可),提示用户输入Y/y或N/n来选择动态优先权法还是轮转法。
注意,这里的两个输出语句一定要写在各自的输入语句的前面,要不然用户在操作界面需要先输入才能看到提示语句,显然这样与我们想要的功能不一致
同时我们要考虑到代码的鲁棒性,由于字符串的特殊性,用户很可能输入错误,所以我们在if条件语句里就将用户输错可能性考虑上,在输入不为Y/y/N/n时给出输出提示。
我们这边开始使用逐步求精法,先将代码最简单化,将所有动态数据全部静态化,用常量定义每个进程的优先级、需要运行时间,这样输出一组静态的数据,易于我们刚开始这个算法的实现,而后再逐步将常量都实现随机数化。
int a[5] = { 0,2,2,2,2 };//第一个数值不用
multimapprocesslist;
processlist.insert(pair(1, 3));//1对应的是优先级、3对应的是进程编号
processlist.insert(pair(3, 2));
processlist.insert(pair(2, 1));
processlist.insert(pair(4, 4));
while(processlist.empty()==false){
for (auto list = processlist.begin(); list != processlist.end(); list++)
cout << "进程编号" << list->second << "的进程优先级为" << list->first << ",状态为:就绪" << endl;
cout << "进程编号" << processlist.begin()->second << "的进程状态变为:进行" << endl;
a[processlist.begin()->second] -= 1;
if (a[processlist.begin()->second] == 0)
{
cout << "进程编号" << processlist.begin()->second << "的状态变为:完成" << endl;
}
else
{
processlist.insert(pair((processlist.begin()->first) + 1, processlist.begin()->second));
}
processlist.erase(processlist.begin());
}
对于这个这个模块的代码一开始我也没有完善界面显示美观问题,这个也一并放入后面的逐步求精里面。
首先解释一下这里用到的map模板:map是c++的一个标准容器,它提供了很好一对一的关系,所以在这里我们用进程编号与优先级一一对应非常好用,同时更好的是因为map会自动根据元素值排序,这样可以省掉我们的排序算法,map直接输出的就是按优先级排好的队列。(关于map函数的基本用法可以搜索“C++map”就可以搜到相关算法,难度不高,搜索相关教学视频也能更好地理解)
首先我们定义一个数组来存放每个进程需要的运行时间,这里我们直接将进程数量默认为4,所以建立一个a[5]数组,至于为什么是5呢,因为这样放弃a[0]的值后,可以将a[1]、a[2]、a[3]、a[4]与进程1、2、3、4一一对应,更便于我们后续代码的编写。然后我们将每个程序的需要运行时间都设置为2,这个数值用来测试我个人觉得真的很好,因为2的话每个进程就需要调出两次,可以非常完美地检测是否能正常调入,如果设置为1,那就只有一次调出没有调入,而设置为3的话明显又显得重复繁琐,并不需要3次调出和2次调入来测试。(时间设置与调入/调出次数的关系如下表所示)按测试来说,只需调入调出各至少有一次即可。
时间的设置 | 调入次数 | 调出次数 |
---|---|---|
1 | 0 | 1 |
2 | 1 | 2 |
3 | 2 | 3 |
multimapprocesslist;
随后我们构造一个multimap,为什么这边要使用multimap,是因为在运行过程中优先级会变化,而普通map中元素值不能重复,如果重复就会出现直接丢失的情况,所以如果我们直接使用map,在优先级变为一样时的两个进程就有一个直接消失了。而multimap其实就是一个可以重复元素的map(注意这里的multi不要拼错)。processlist是我定义的这个map的名字,可以根据自己的需要随意更改,这边由于我们在做进程调度,所以简易地翻译成进程(process)列表(list)。
继续,我们插入四个进程,第一个元素定义为优先级,第二个元素定义为进程编号,这样定义有什么好处呢:很简单,因为map的自动排序是按照第一个元素的大小数值的,所以将第一个元素定义为优先级可以直接发挥map的自动排序功能为我们排序进程队列,而输出进程编号和优先级数值时,可以轻松地交换位置,并不影响输出顺序。
while(processlist.empty()==false)
这个语句用来控制程序的循环结束,表达的意思就是一旦map里所有元素都删除了,相应的进程就都完成了。
for (auto list = processlist.begin(); list != processlist.end(); list++)
这句代码是遍历整个map,很好理解,processlist.begin()找到的就是第一个进程,相对的end就是最后一个。同时这边定义了局部变量list,主要也是为了遍历map,具体实现方法应该还有其他的,由于我也是现学的map,觉得这一种比较好理解。
list->second一眼就能看出是指向第二个元素,比如pair
到这里,我们就完成了整个进程队列的创建和展示,接下来就是实现进程调度了:
cout << "进程编号" << processlist.begin()->second << "的进程状态变为:进行" << endl;
这里一开始我还是想贯彻逐步求精,先将这里的processlist.begin()->second直接写成processlist.find(1),按道理来说静态的数值更直观,直接找到优先级为1的进程然后状态变为进行,但是我在编写的时候发现这样写有一个问题,优先级在继续深入运行的过程中,会变成2、3、4,如果每次根据具体数值变换的话,需要每运行一个循环就跑一遍代码输出看一下下一个优先级最高的是哪个进程,然后才能选取到将要进行的进程。所以我这里直接将processlist.find(1)定义为processlist.begin()->second,也非常易懂,还直接实现了每个循环中都可以使用这个代码,对于begin刚刚讲过就是map最前端的进程,而我们要选取的就是最前端的进程,而->second指向second是因为我们将第二个元素定义为了进程编号,千万不要指到first,要不然指向的是优先级,虽然能跑通,但界面就全乱了。
a[processlist.begin()->second] -= 1;
这边之前定义数组时将编号与数组一一对应的好处就体现出来了,直接用map里的编号赋值给数组,将对应进程的所需时间-1。
if (a[processlist.begin()->second] == 0)
{
cout << "进程编号" << processlist.begin()->second << "的状态变为:完成" << endl;
}
else
{
processlist.insert(pair((processlist.begin()->first) + 1, processlist.begin()->second));
}
processlist.erase(processlist.begin());
随后就需要用一个if来判断,如果所需时间为0了,就不需要再进入进程队列了,直接输出这个进程完成了,如果还没为0,就将刚刚的优先级+1,编号不变的一个新元素插入map,然后再删除第一个元素。这里不在插入前删除,是因为优先级+1后的新元素一定排在刚刚运行过的进程的后面,所以可以放心先插入再删除,以便使用删除前的数值,而不需要再找变量来存储赋值。
这里给一个跑代码的截图:
至此我们用常量完成了动态优先级算法模块,说是常量,其实大部分我们都已经实现了自动化,没有直接去抓取常量,接下来要做的就是将常量用随机数赋值就行了,由此可见用map来实现进程调度确实是个好选择呀!
接下来我稍微改动一下代码,用循环将原本的常量赋值为随机数,很快便将求精的代码跑通了:
int a[10];
for (int num = 1; num <= n; num++)
{
a[num] = random(4) + 1;
}
multimapprocesslist;
for (int num = 1; num <= n; num++)
{
processlist.insert(pair(random(4)+1, num));
}
while(processlist.empty()==false){
for (auto list = processlist.begin(); list != processlist.end(); list++)
cout << "进程编号" << list->second << "的进程优先级为" << list->first << ",状态为:就绪" << endl;
cout << "进程编号" << processlist.begin()->second << "的进程状态变为:进行" << endl;
a[processlist.begin()->second] -= 1;
if (a[processlist.begin()->second] == 0)
{
cout << "进程编号" << processlist.begin()->second << "的状态变为:完成" << endl;
}
else
{
processlist.insert(pair((processlist.begin()->first) + 1, processlist.begin()->second));
}
processlist.erase(processlist.begin());
}
首先同样地定义一个整型数组存放各个进程所需运行时间,同样的因为要放弃a[0]所以根据进程数n的数量是4~8应该定为9,我这边又多给了一个余量,写了10,如果需要更多进程则可以定义更多。所需时间我定义在了一秒至四秒,这个也可以根据自己的需要定,不过最好不要太大否则会出现输出太多的情况。
然后我们要做的就是把map中的编号用循环从1给到n,同时也给每个进程随机出了优先级,优先级我也定义为了1~4,这个也是不要定太大,要不然每个进程的优先级差别很大,很可能都没有顺序的变换,每次都是第一个进程执行到结束,然后再下一个进程。
就这样我们很轻松就对上一步代码进行了求精,跑的结果我放两个在下面:
第一张是跑4个进程,第2~4张是跑8个进程,可见8个进程的输出是成指数增加的,所以最好不要输入超过8个的进程。
到这里,我们已经基本完成了动态优先级算法,接下来我们只需要对用户界面进行求精即可,这个阶段只要细致一些不缺少分号已经不会出错啦。
动态优先级算法放入完整代码中的效果:
#include
#include
#include
由于轮转法的实现相较于动态优先级算法更为简单,同时有了前面算法的铺垫,我就不逐句讲解了,将最终代码附上并加一个轮转法跑通的截图:
#include
#include
#include
processlist.insert(pair((processlist.begin()->first) + n, processlist.begin()->second));
唯一要讲的就是这个思路,轮转法可见我直接用了map而不是multimap,那我为什么确定不会出现相同的优先级呢,是因为我的这一句代码(如果将我代码整个复制入vs2019应该是在66行),这个语句让每个进程输出后的优先级直接加n,这样能确保运行过的进程直接排到队尾,而且还不可能出现相同的优先级致使元素丢失。
第一次写CSDN的博客,也只是一个大二学生,很多地方说的不好大家多多包涵,如果有更好的改进可以联系我一起探讨,感谢看到这里的读者,谢谢!