Python 实现单道处理系统的作业调度模拟程序 - 操作系统

本文发布且更新于个人博客 https://www.xerrors.fun/JobSchedulingProgram/

我相信一个好的算法应该是优美的,不需要过多的解释,代码前后自成一体;这也是我向往的目标,不过从目前的情况来看,我还是需要努力的。

这次的作业是「单道处理系统的作业调度模拟程序」要求实现 3 种作业调度算法:

  • 先来先服务(FCFS)
  • 最短作业优先(SJF)
  • 响应比高者优先(HRRN)

对每种调度算法都要求打印每个作业开始运行时刻、完成时刻、周转时间、带权周转时间,以及这组作业的平均周转时间及带权平均周转时间,以比较各种算法的优缺点。

三种调度算法的原理

假设咱们学生就是一个「单道处理系统」(谦虚),当面对很多科作业到来的时候,有以下几种应对办法

FCFS 先来先服务:先布置的作业优先做,所有作业排好队,后来的作业放在最后面;

最短作业优先:哪个省事先做哪个,所有作业按照要花费的时间排成一个序列;

响应比高者优先:如果哪个先来先做哪个,可能导致比较紧急且简单的工作没法完成,比如老师让当堂交 3 道选择题,这推到后面是不合适的;如果使用最短作业优先呢?可能刚一开始留下的一个比较繁琐的任务一直没法做;比如说暑假开始了,你要准备学车,但是又想出去玩,又想打游戏,等到暑假结束,还是没去学车(亲身经历);所以要综合考虑等待时间以及处理的时间开销。

= + 优先级 = \frac{等待的时间 + 需要花费的时间} {需要花费的时间}

Python 实现

JCB 的数据结构

每个作业由一个作业控制块JCB表示,JCB可以包含如下信息:作业名、提交时间、所需的运行时间、所需的资源、作业状态、链指针等等;所以最终的 JCB 类就直接创建出来了,千言万语不如直接看代码

class JCB:
    # 表示系统中的资源数量,其中 1 表示独占资源
    srcs = [3, 2, 4, 1, 1, 1]
    clock = 0 # 类的时钟
    def __init__(self, name, time, srcs=None):
        self.name = name     # 名称
        self.time = time     # 所需时间
        self.status = 'Wait' # 作业的状态
        self.commit_time = JCB.clock # 作业到达时间

        # 所需的资源,单道程序调度用不到的
        self.srcs = srcs if srcs else [0] * len(JCB.srcs)
        self.pointer = None  # 链指针,不知道要用来干嘛
        self.start_time = -1 # 作业开始执行的时间,未执行为 -1 

        self.rp = 0 # 优先级

        JCB.clock += 1 # 类的时钟 +1

    # 计算优先级,越大优先级越高,越靠前
    def calcRp(self, clock):
        if self.start_time == -1:
            self.rp = (clock - self.commit_time + self.time) / self.time

其中 calcRp 函数会根据「调度程序」当前的时间 clock 来计算该作业的优先级。

初始化 JSB

又是使用随机数生成作业;同样这次在创建数组的时候是使用了快速创建数组的方法:

a = [i for i in range(n)]
# 等同于
a = []
for i in range(n):
    a.append(i)

从上面的类中可以看到,必要的参数就两个,一个是名称,一个是完成作业所需的时间;作业的到达时间我们直接使用 JSB 类的属性 clock 直接赋值。

# 初始化所有 JCB
def init():
    # 先考虑单程序的情况,也就是不需要考虑资源的情况
    jsb_list = [JCB(chr(ord('A') + i), random.randrange(2, 6)) for i in range(random.randrange(5, 9))]
    for job in jsb_list:
        print('名称:{}\t到达时间:{}\t所需时间:{}\t所需资源:{}'.format(
            job.name,
            job.commit_time,
            job.time,
            job.srcs
        ))
    print()
    return jsb_list

运行结果:

名称:A 到达时间:0     所需时间:3     所需资源:[0, 0, 0, 0, 0, 0]
名称:B 到达时间:1     所需时间:2     所需资源:[0, 0, 0, 0, 0, 0]
名称:C 到达时间:2     所需时间:5     所需资源:[0, 0, 0, 0, 0, 0]
名称:D 到达时间:3     所需时间:5     所需资源:[0, 0, 0, 0, 0, 0]
名称:E 到达时间:4     所需时间:4     所需资源:[0, 0, 0, 0, 0, 0]
名称:F 到达时间:5     所需时间:4     所需资源:[0, 0, 0, 0, 0, 0]
名称:G 到达时间:6     所需时间:4     所需资源:[0, 0, 0, 0, 0, 0]
名称:H 到达时间:7     所需时间:5     所需资源:[0, 0, 0, 0, 0, 0]

调度算法的实现

其实这三种算法的核心是一样的,只是对作业的「比较方法」不一样,也就是说,这三种算法可以使用同一个函数,只是在「初始化堆」的时候使用的比较函数是不一样的。

所以定义两个函数,一个是实现算法的核心功能 func,另外一个函数是根据调度方法的不同提供不同的比较函数 getLowerThan

def func(jobs, method):
    JCB.__lt__ = getLowerThan(method)
    ...
    # 下面的内容先省略
    return None

def getLowerThan(method):
    # 我感觉这个函数写的很丑,但是又没啥好办法
    def FCFS_lt(self, other):
        return self.commit_time < other.commit_time
    def SJF_lt(self, other):
        return self.time < other.time
    def HRRN_lt(self, other):
        return self.rp > other.rp

    if method == 'FCFS':
        return FCFS_lt
    elif method == 'SJF':
        return SJF_lt
    elif method == 'HRRN':
        return HRRN_lt

我们把类 JCB 的「小于号」进行了运算符重构,所以我们在使用 heapq.heapify(array)的时候会调用重构的函数进行计算。

那么最后,整个算法就差最核心的算法没提到了,下面是简化之后的代码,删除了一些对周转时间的计算以及输出,完整代码见文末;因为要考虑作业的到达情况,所以整个函数里面有两个序列,一个是完整的作业序列 jobs,当时钟 「clock」 到达某个作业的「到达时间」的时候,将这个作业从 jobs 放到 jobs_heap 里面:

def func(jobs, method):
    JCB.__lt__ = getLowerThan(method)

    # 初始化,需要考虑作业的到达时间
    clock = 0
    currentJob = None # 当前任务
    jobs_heap = []

    while len(jobs) != 0 or len(jobs_heap) != 0 or currentJob.status != 'Finish':
        # 作业到达并入堆
        while len(jobs) > 0 and clock == jobs[0].commit_time:
            heapq.heappush(jobs_heap, jobs.pop(0))

        # 计算优先级
        if method == 'HRRN':
            for i in jobs_heap:
                i.calcRp(clock)
        heapq.heapify(jobs_heap)

        # 判断当前是否有任务或者当前任务额是否已完成
        if not currentJob or currentJob.status == 'Finish':
            currentJob = heapq.heappop(jobs_heap)
            currentJob.start_time = clock

        # 如果有任务,当前任务所需时间 - 1    
        currentJob.time -= 1
        clock += 1

        # 判断当前任务是否结束
        if currentJob.time == 0:
            currentJob.status = 'Finish'
    return None

需要注意的是入堆那部分代码,采用高响应优先的时候,需要根据当前时间实时计算优先级,所以每一次循环(时间片)都要重新计算优先级,然后变成优先队列。

完整代码以及参考资料

import random
import heapq
import copy

class JCB:
    # 表示系统中的资源数量,其中 1 表示独占资源
    srcs = [3, 2, 4, 1, 1, 1]
    clock = 0 # 类的时钟
    def __init__(self, name, time, srcs=None):
        self.name = name     # 名称
        self.time = time     # 所需时间
        self.status = 'Wait' # 作业的状态
        self.commit_time = JCB.clock # 作业到达时间

        # 所需的资源,单道程序调度用不到的
        self.srcs = srcs if srcs else [0] * len(JCB.srcs)
        self.pointer = None  # 链指针,不知道要用来干嘛
        self.start_time = -1 # 作业开始执行的时间,未执行为 -1 

        self.rp = 0 # 优先级

        JCB.clock += 1 # 类的时钟 +1

    # 计算优先级,越大优先级越高,越靠前
    def calcRp(self, clock):
        if self.start_time == -1:
            self.rp = (clock - self.commit_time + self.time) / self.time


# 初始化所有 JCB
def init():
    # 先考虑单程序的情况,也就是不需要考虑资源的情况
    jsb_list = [JCB(chr(ord('A') + i), random.randrange(2, 6)) for i in range(random.randrange(5, 9))]
    for job in jsb_list:
        print('名称:{}\t到达时间:{}\t所需时间:{}\t所需资源:{}'.format(
            job.name,
            job.commit_time,
            job.time,
            job.srcs
        ))
    print()
    return jsb_list


def func(jobs, method):
    JCB.__lt__ = getLowerThan(method)
    # 保存作业的数量用于计算平均周转时间
    job_count = len(jobs)

    # 初始化,需要考虑作业的到达时间
    clock = 0
    currentJob = None # 当前任务
    jobs_heap = []
    # 总周转时间以及带权周转时间
    turnover_time = 0
    turnover_time_wei = 0

    while len(jobs) != 0 or len(jobs_heap) != 0 or currentJob.status != 'Finish':
        # 作业到达并入堆
        while len(jobs) > 0 and clock == jobs[0].commit_time:
            heapq.heappush(jobs_heap, jobs.pop(0))

        # 计算优先级
        if method == 'HRRN':
            for i in jobs_heap:
                i.calcRp(clock)
        heapq.heapify(jobs_heap)

        # 判断当前是否有任务
        if not currentJob or currentJob.status == 'Finish':
            currentJob = heapq.heappop(jobs_heap)
            currentJob.start_time = clock

        currentJob.time -= 1
        clock += 1

        # 如果有任务,当前任务所需时间 - 1
        # 判断当前任务是否结束
        if currentJob.time == 0:
            # 计算周转时间
            cur_turnovertime = clock - currentJob.commit_time
            # 计算带权周转时间
            cur_turnovertime_wei = cur_turnovertime / (clock - currentJob.start_time)
            # 修改状态为完成状态
            currentJob.status = 'Finish'
            print('作业{}运行时间:{}\t完成时间:{}\t周转时间:{}\t带权周转时间{:.4f}'.format(
                currentJob.name,
                clock - currentJob.start_time,
                clock,
                cur_turnovertime,
                cur_turnovertime_wei))
            turnover_time += cur_turnovertime
            turnover_time_wei += cur_turnovertime_wei
    print('{} 的平均周转时间为:{:.3f}\t带权平均周转时间:{:.3f}\n'.format(
        method,
        turnover_time / job_count,
        turnover_time_wei / job_count))
    return None


def getLowerThan(method):
    def FCFS_lt(self, other):
        return self.commit_time < other.commit_time
    def SJF_lt(self, other):
        return self.time < other.time
    def HRRN_lt(self, other):
        return self.rp > other.rp

    if method == 'FCFS':
        return FCFS_lt
    elif method == 'SJF':
        return SJF_lt
    elif method == 'HRRN':
        return HRRN_lt


def main():
    jobs = init()
    for method in ['FCFS', 'SJF', 'HRRN']:
        func(copy.deepcopy(jobs), method)

if __name__ == '__main__':
    main()

运行结果:

名称:A 到达时间:0     所需时间:4     所需资源:[0, 0, 0, 0, 0, 0]
名称:B 到达时间:1     所需时间:4     所需资源:[0, 0, 0, 0, 0, 0]
名称:C 到达时间:2     所需时间:4     所需资源:[0, 0, 0, 0, 0, 0]
名称:D 到达时间:3     所需时间:3     所需资源:[0, 0, 0, 0, 0, 0]
名称:E 到达时间:4     所需时间:3     所需资源:[0, 0, 0, 0, 0, 0]
名称:F 到达时间:5     所需时间:2     所需资源:[0, 0, 0, 0, 0, 0]
名称:G 到达时间:6     所需时间:2     所需资源:[0, 0, 0, 0, 0, 0]
名称:H 到达时间:7     所需时间:2     所需资源:[0, 0, 0, 0, 0, 0]

作业A运行时间:4        完成时间:4     周转时间:4     带权周转时间1.0000
作业B运行时间:4        完成时间:8     周转时间:7     带权周转时间1.7500
作业C运行时间:4        完成时间:12    周转时间:10    带权周转时间2.5000
作业D运行时间:3        完成时间:15    周转时间:12    带权周转时间4.0000
作业E运行时间:3        完成时间:18    周转时间:14    带权周转时间4.6667
作业F运行时间:2        完成时间:20    周转时间:15    带权周转时间7.5000
作业G运行时间:2        完成时间:22    周转时间:16    带权周转时间8.0000
作业H运行时间:2        完成时间:24    周转时间:17    带权周转时间8.5000
FCFS 的平均周转时间为:11.875   带权平均周转时间:4.740

作业A运行时间:4        完成时间:4     周转时间:4     带权周转时间1.0000
作业E运行时间:3        完成时间:7     周转时间:3     带权周转时间1.0000
作业H运行时间:2        完成时间:9     周转时间:2     带权周转时间1.0000
作业G运行时间:2        完成时间:11    周转时间:5     带权周转时间2.5000
作业F运行时间:2        完成时间:13    周转时间:8     带权周转时间4.0000
作业D运行时间:3        完成时间:16    周转时间:13    带权周转时间4.3333
作业C运行时间:4        完成时间:20    周转时间:18    带权周转时间4.5000
作业B运行时间:4        完成时间:24    周转时间:23    带权周转时间5.7500
SJF 的平均周转时间为:9.500     带权平均周转时间:3.010

作业A运行时间:4        完成时间:4     周转时间:4     带权周转时间1.0000
作业B运行时间:4        完成时间:8     周转时间:7     带权周转时间1.7500
作业D运行时间:3        完成时间:11    周转时间:8     带权周转时间2.6667
作业F运行时间:2        完成时间:13    周转时间:8     带权周转时间4.0000
作业G运行时间:2        完成时间:15    周转时间:9     带权周转时间4.5000
作业H运行时间:2        完成时间:17    周转时间:10    带权周转时间5.0000
作业E运行时间:3        完成时间:20    周转时间:16    带权周转时间5.3333
作业C运行时间:4        完成时间:24    周转时间:22    带权周转时间5.5000
HRRN 的平均周转时间为:10.500   带权平均周转时间:3.719

猜你喜欢

转载自blog.csdn.net/jaykm/article/details/106262132