常用的调度算法

调度算法是什么:

根据系统的资源分配策略所规定的资源分配算法。对于不同的系统和系统目标,通常采用不同的调度算法。

先来先服务(First Come First Served,FCFS)

一种最简单的调度算法,在进程中采用FCFS算法时,则每次调度是从就绪队列中选择一个最先进入该队列的进程,为之分配处理机。

最短作业优先(Shortest Job First,SJF)

每次从就绪队列中选出一个估计运行时间最短的作业,为它分配处理机。
SJF是改进版本的FCFS,在短作业优先前提下,短作业不必等待长作业,从而可以处理更多的短作业。

优先权调度算法

高响应比优先调度算法(Highest Response Ratio Next,HRRN)

HRRN是对FCFS和SJF的一种综合改进,FCFS只考虑等待时间而未考虑执行时间,SJF方式只考虑执行时间而未考虑等待时间,HRRN同时考虑作业的等待时长和估计执行时长,从中选出响应比最高的作业执行。

响应比R: (W+T)/T = 1+W/T
T:作业估计执行时长
W:作业等待时长

基本流程:每当进行作业调度时,系统计算每个作业的响应比R,选择其中R最大者执行

时间片轮转调度算法(RR,Round-Robin)

RR是一种最古老、最简单、最公平且使用最广的算法,每个进程会被分配一个时间片(进程的执行时间)。

基于FCFS算法,给予每个进程一个时间片,当一个进程的时间片用完时,将发生时钟中断,调度程序暂停运行进程,将进程送到就绪队列队尾,接着通过上下文执行就绪队列队首的进程。

系统将所有的就绪进程按照先来先服务的原则排成一个队列,每次调度时,让CPU为队列的首进程服务一个时间片(几ms 到几百ms),当时间片到了时,由一个计时器发出时钟中断请求,调度程序就以此信号停止进程,并将停止了的进程送往就绪队列的末尾。

如何确定进程的时间片:

  • 系统对进程响应时间的要求
  • 就绪队列进程的数量
  • 系统的性能

多级反馈队列调度算法

目前被公认的一种较好的进程调度算法,Unix系统采取的是这种调度算法,可以满足短进程和长进程的要求。

  • 设置多个就绪队列,从第一个队列到最后一个队列,优先权从高往低,时间片长度从成倍递增。

  • 当新进程进入内存后,首先放在第一队列的队尾,按FCFS原则等待调度,如果在分配的时间片中进程可以完成,则准备撤离系统,否则调度到第二队列的队尾,再同样按FCFS的原则等待调度执行,如此下去。

  • 当第1(i-1)队列均空时,才会调度第i队列中的进程运行。又有新的进程进入优先权较高的队列(第1(i-1)中的任何一个队列),则正在运行的进程放回到第i队列的队尾,此时处理机将为新进程服务。

各类调度算法优缺点

优点 缺点
FCFS 利于长作业和CPU繁忙工作 不利于短作业和I/O繁忙工作
SJF 有利于系统的吞吐量。 长作业的运行得不到保障
HRRN 综合了FCFS和SJF算法的优点,兼顾了长进程和短进程的调度执行。 每次调度前需计算响应比,会给系统带来开销
RR 简单易行,平均响应时间短 不利于紧急作业,时间片会影响系统性能

代码实现:

# -*- coding: utf-8 -*-
"""
Created on Sun Nov  1 14:25:56 2020
@author: 11099

设数据是以表格的形式存在,包含的属性有进程名、到达时间、服务时间、开始时间、完成时间、周转时间、带权周转时间、平均值
所有算法均遵从以下步骤:
1.准备好数据(要求输入进程名、到达时间、服务时间,并准备好其他属性的变量)
2.实现算法(该步骤生成开始时间、完成时间、周转时间、带权周转时间、平均值的具体数值)
3.展示结果(都是两位有效数字的浮点数)
"""

import pandas as pd
# 定义调度算法的类
class schedule_algorithm:
    # 初始化方法
    def __init__(self, name, arrived_time, served_time):
        self.data = pd.DataFrame({'到达时间': arrived_time, '服务时间': served_time}, dtype='float16', index=name, columns=['到达时间', '服务时间', '开始时间', '完成时间', '周转时间', '带权周转时间'])
        self.data.sort_values(['到达时间', '服务时间'], inplace=True)  # 先按到达时间从小到大排序,再按服务时间进行微调,意义在于同一到达时间可以先按服务时间进行调度
        self.data.iloc[0, 2], self.data.iloc[0, 3], self.data.iloc[0, 4], self.data.iloc[0, 5] = self.data.iloc[0, 0], self.data.iloc[0, 0]+self.data.iloc[0, 1], self.data.iloc[0, 1], 1.
    
    # 计算过程,单独写成一个方法方便算法的调用
    def cal(self, code):
        data, finish_time = self.data.copy(), self.data.iloc[0, 3].copy()
        for idx in data.index[1:]:
            ind = (data.loc[data['开始时间'].isnull(), '到达时间'] <= finish_time)
            if ind.any():
                exec(code)
                data.loc[self.ind, '开始时间'], 
                data.loc[self.ind, '完成时间'], 
                data.loc[self.ind, '周转时间'], 
                data.loc[self.ind, '带权周转时间'], 
                finish_time = finish_time, 
                finish_time+data.loc[self.ind, '服务时间'], 
                finish_time+data.loc[self.ind, '服务时间']-data.loc[self.ind, '到达时间'], 
                (finish_time+data.loc[self.ind, '服务时间']-data.loc[self.ind, '到达时间'])/data.loc[self.ind, '服务时间'], finish_time+data.loc[self.ind, '服务时间']
            else:
                data.loc[idx, '开始时间'], data.loc[idx, '完成时间'], data.loc[idx, '周转时间'], 
                data.loc[idx, '带权周转时间'], finish_time = data.loc[idx, '到达时间'], 
                data.loc[idx, '到达时间']+data.loc[idx, '服务时间'], 
                data.loc[idx, '服务时间'], 1., 
                data.loc[idx, '到达时间']+data.loc[idx, '服务时间']
        		data.loc['平均'] = data.mean()
        return data.applymap('{:.2f}'.format)
        
    # 先来先服务调度算法,基本思想是按进程或作业到达的前后顺序进行调度
    def FCFS(self):
        return self.cal('''self.ind = data.loc[ind[ind].index].index[0]''')  # 返回第一个值进行计算,先来先服务调度
    # 短作业优先是指对短作业或短进程优先调度的算法。设计目标是改进FCFS算法,减少作业或进程的平均周转时间
    def SJF(self):
        return self.cal('''self.ind = data.loc[ind[ind].index, '服务时间'].idxmin()''')  # 返回第一个最小值,若多个服务时间相同,则相当于按先来先服务调度
    # 响应比高者优先算法,基本思想是计算响应比(即1+等待时间/服务时间),选择响应比最高的进程运行
    def HRRN(self):
        return self.cal('''self.ind = (1+(finish_time-data.loc[ind[ind].index, '到达时间'])/data.loc[ind[ind].index, '服务时间']).idxmax()''')  # 返回第一个最大值,若多个响应比相同,则相当于按响应比中的先来先服务调度
    # 时间片轮转调度算法
    def RR(self):
        pass
    # 优先权调度算法
    def Priority(self):
        pass
    # 多级反馈队列调度算法
    def MFQ(self):
        pass

# 只能在本文件中运行的代码块(即python的main文件名)
if __name__ == '__main__':
    def input_data():
        name = input('请输入程序名(空格隔开):').split()
        arrived_time = input('请输入到达时间(空格隔开):').split()
        served_time = input('请输入服务时间(空格隔开):').split()
        try:
            s_a = schedule_algorithm(name, arrived_time, served_time)
            return s_a
        except:
            raise Exception('数据输入错误!')
    s_a = input_data()
    while True:
        print('
请选择调度算法(1~6 or y,n):
1.先来先服务
2.短作业优先
3.响应比高者优先
4.时间片轮转
5.优先权
6.多级反馈队列
y.重新输入数据
n.退出')
        option = input('输入:')
        if option == 'n':
            break
        elif option == '1':
            fcfs = s_a.FCFS()
            print('对进程按先来先服务调度:
{}
{}'.format(fcfs, '-' * 30))
        elif option == '2':
            sjf = s_a.SJF()
            print('对进程按短作业优先调度:
{}
{}'.format(sjf, '-' * 30))
        elif option == '3':
            hrrn = s_a.HRRN()
            print('对进程按响应比高者优先调度:
{}
{}'.format(hrrn, '-' * 30))
        elif option == '4':
            print('对进程按时间片轮转调度:
{}
{}'.format(None, '-' * 30))
        elif option == '5':
            print('对进程按优先权调度:
{}
{}'.format(None, '-' * 30))
        elif option == '6':
            print('对进程按多级反馈队列调度:
{}
{}'.format(None, '-' * 30))
        elif option == 'y':
            s_a = input_data()
        else:
            print('命令输入错误!')

package test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;

public class processMenu {

    ArrayList<JCB> jcb;// 存放所有进程
    LinkedList<JCB> link;// 存放已经进入队列的进程
    ArrayList<JCB> new_jcb;// 存放按指定调度算法排序后的进程
    JCB nowProess;// 当前应执行进程

    public void init() {//初始化
        jcb = new ArrayList<JCB>();
        link = new LinkedList<JCB>();
        new_jcb = new ArrayList<JCB>();
        JCB p1 = new JCB("1", 0, 4, 3);
        JCB p2 = new JCB("2", 1, 3, 2);
        JCB p3 = new JCB("3", 2, 5, 3);
        JCB p4 = new JCB("4", 3, 2, 1);
        JCB p5 = new JCB("5", 4, 4, 5);
        jcb.add(p1);
        jcb.add(p2);
        jcb.add(p3);
        jcb.add(p4);
        jcb.add(p5);
        //先将jcb排序,便于下面的算法实现,就不需要再定义一个标识进程是否已到达的boolean,即无需每次都从头开始扫描jcb容器,
        //而是用一个K记录下当前已经扫描到的位置,一次遍历即可,提高了算法效率。
        Collections.sort(jcb, new compareAt_St());
    }

    public void FCFS() {//先来先服务算法
        ProcessQueue pq = new ProcessQueue();//调用内部类
        pq.EnqueueLast();//让最先到达的进程先入队
        System.out.println("*****************************************************");
        while (!link.isEmpty()) {//while(new_jcb.size()!=jcb.size())
            pq.DisplayQueue();//打印当前队列中的进程
            pq.Dequeue();//出队,一次一个
            pq.EnqueueLast();//已到达的进程入队
        }
    }

    public void SJF() {// 短作业优先算法
        ProcessQueue pq = new ProcessQueue();
        pq.EnqueueLast();
        System.out.println("*****************************************************");
        while (!link.isEmpty()) {
            pq.DisplayQueue();//打印当前队列中的进程
            pq.Dequeue();//出队,一次一个
            pq.EnqueueLast();//已到达的进程入队
            Collections.sort(link, new compareSt());//队列中的进程还需按服务时间长度进行排序
        }
    }

    public void RR() {//时间片轮转调度算法
        ProcessQueue pq = new ProcessQueue();
        pq.EnqueueLast();
        System.out.println("*****************************************************");
        while (!link.isEmpty()) {
            pq.DisplayQueue();//打印当前队列中的进程
            pq.Dequeue(1);//出队,一次一个,因为上一轮出的得让刚到达的进程先进队列,所以没办法,进队操作只能也放在这个函数里了
        }
    }

    public void HRN() {//最高响应比优先调度算法
        ProcessQueue pq = new ProcessQueue();
        pq.EnqueueLast();
        System.out.println("*****************************************************");
        while (!link.isEmpty()) {
            pq.DisplayQueue();//打印当前队列中的进程
            pq.Dequeue();//出队,一次一个
            pq.EnqueueLast();//已到达的进程入队
            Collections.sort(link, new comparePriority());//队列中的进程还需按响应比进行排序
        }
    }

    class ProcessQueue {
        int k = 0;// jcb中的进程遍历时的下标
        int nowTime = 0;// 当前时间
        double sliceTime;//轮转调度时间片
        int i = 0;//记录当前出入队列的次数

        public void EnqueueLast() {//进程首次入队,可一次进多个,从队尾进入
            while (k < jcb.size()) {//当遍历完jcb中的所有进程时结束
                if (jcb.get(k).arriveTime <= nowTime) {//已经到达的进程按到达时间先后进入队列
                    link.addLast(jcb.get(k));
                    k++;
                } else {
                    break;//如果该进程还未入队,即先结束遍历,保留当前下标k值,注意:此处不要k--;
                }
            }
        }

        public void EnqueueFirst() {//进程首次入队,可一次进多个,从队首进入
            while (k < jcb.size()) {//当遍历完jcb中的所有进程时结束
                if (jcb.get(k).arriveTime <= nowTime) {//已经到达的进程按到达时间先后进入队列
                    link.addFirst(jcb.get(k));
                    k++;
                } else {
                    break;//如果该进程还未入队,即先结束遍历,保留当前下标k值,注意:此处不要k--;
                }
            }
        }

        public void Dequeue() {//进程出队,一次只出一个
            nowProess = link.removeFirst();//移除队列的队首元素并且返回该对象元素
            nowProess.beginTime = nowTime;//计算开始时间,即为上一个进程的结束时间
            nowProess.finshTime = nowProess.beginTime + nowProess.serveTime;//计算结束时间,该进程开始时间+服务时间
            nowProess.roundTime = nowProess.finshTime - nowProess.arriveTime;//计算周转时间
            nowProess.aveRoundTime = (double) nowProess.roundTime / nowProess.serveTime;//计算平均周转时间
            nowTime = nowProess.finshTime;//获得结束时间,即当前时间,方便判断剩下的进程是否已到达
            new_jcb.add(nowProess);//经处理过数据后加入new_jcb容器
            for (int i = 0; i < link.size(); ++i) {
                link.get(i).waitTime++;//所有进入等待队列的进程等待时间+1,此处只为最高响应比算法所用
            }
        }

        public void Dequeue(double sliceTime) {//重载Dequeue方法,实现轮转调度算法的出队
            nowProess = link.removeFirst();//移除队列的队首元素并且返回该对象元素
            if (nowProess.firstTimeTag == false) {
                /*轮转调度进程可能会多次反复进出队列,不像FCFS和SJF的进程只会进出一次,所以计算开始时间可以设个标志位,让每个进程在
				 * 第一次执行时记录一遍即可*/
                nowProess.beginTime = nowTime;//进程开始执行的时间
                nowProess.firstTimeTag = true;//计算第一次即可,下次无需更新计算
            }
            nowTime += sliceTime;//每次出队,用时一个时间片,更新当前时间
            nowProess.clock += sliceTime;//更新当前出队列的进程已服务时间
            if (nowProess.clock >= nowProess.serveTime) {
                nowProess.finshTime = nowTime;//计算该进程完成时间
                nowProess.roundTime = nowProess.finshTime - nowProess.arriveTime;//计算周转时间
                nowProess.aveRoundTime = (double) nowProess.roundTime / nowProess.serveTime;//计算平均周转时间
                new_jcb.add(nowProess);//经处理过数据后加入new_jcb容器
                EnqueueFirst();//已到达的进程先入队
            } else {
                EnqueueFirst();//已到达的进程先入队
                link.addLast(nowProess);//上一轮出的再紧接着进入队尾
            }
        }

        public void DisplayQueue() {//队列中等候的进程
            i++;
            System.out.println("第" + i + "次队列中排队的进程:" + link);
        }
    }

    public void printProcess() {
        System.out.println("进程名 到达时间  服务时间   开始时间  完成时间  周转时间  带权周转时间");
        for (int i = 0; i < new_jcb.size(); ++i) {
            System.out.println(" "+"P" + new_jcb.get(i).name + "        " + new_jcb.get(i).arriveTime + "       " +
                    new_jcb.get(i).serveTime + "         " + new_jcb.get(i).beginTime + "         " + new_jcb.get(i).finshTime +
                    "         " + new_jcb.get(i).roundTime + "        " + new_jcb.get(i).aveRoundTime);
        }
        new_jcb.clear();//清空new_jcb容器内的内容,方便存储各种算法的结果并展示
    }
}

class compareSt implements Comparator<JCB> {// 按服务时间升序

    public int compare(JCB arg0, JCB arg1) {
        return arg0.serveTime - arg1.serveTime;
    }
}

class compareAt_St implements Comparator<JCB> {// 按到达时间升序,若到达时间相同,按服务时间升序

    public int compare(JCB o1, JCB o2) {
        int a = o1.arriveTime - o2.arriveTime;
        if (a > 0)
            return 1;
        else if (a == 0) {
            return o1.serveTime > o2.serveTime ? 1 : -1;
        } else
            return -1;
    }
}

class comparePriority implements Comparator<JCB> {//按响应比升序排序

    public int compare(JCB o1, JCB o2) {
        double r1 = (double) o1.waitTime / o1.serveTime;
        double r2 = (double) o2.waitTime / o2.serveTime;
        return r1 > r2 ? 1 : -1;
    }

}

运行结果:

输入数据,并选择调度算法

先来先服务(FCFS)


短作业(SJF)


时间片轮转(RR)

高响应优先(HRRN)


总结

此次调度算法由我们小组成员共同完成,使用Java与Python语言模拟。
组内分工如下

姓名 负责
廖涛 博客园文档撰写
李威剑 使用Python编码实现
邓强 测试用例(Python)
周颖 测试用例(Java)
苏智勇 使用Java编码实现

参考:

https://blog.csdn.net/qq_36143023/article/details/80623662

原文地址:https://www.cnblogs.com/TheFaceOfAutumnWhenSummerEnd/p/13928391.html