MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

公平性在进程调度策略中的体现

2024-07-013.8k 阅读

进程调度与公平性概述

在操作系统的进程管理中,进程调度策略扮演着至关重要的角色。它负责决定哪个进程能够获得 CPU 资源,以及在多个进程竞争资源时如何分配这些资源。公平性作为进程调度策略的一个核心属性,旨在确保每个进程都能合理地共享 CPU 时间,避免某些进程长期占据 CPU 而导致其他进程得不到执行机会。

公平性的体现不仅仅关乎每个进程能否获得执行时间,还涉及到如何在不同类型的进程(如交互式进程和批处理进程)之间实现平衡。从本质上讲,公平的进程调度策略应该根据进程的需求和优先级,以一种无偏见的方式分配 CPU 资源,使得系统整体性能和用户体验都能得到优化。

常见进程调度策略及其公平性分析

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

FCFS 是一种最为简单直观的进程调度策略。它按照进程到达就绪队列的先后顺序来分配 CPU 资源,即先到达的进程先执行。这种策略在实现上非常简单,只需维护一个先进先出(FIFO)的队列即可。

以下是一个简单的 Python 代码示例,模拟 FCFS 调度算法:

class Process:
    def __init__(self, pid, arrival_time, burst_time):
        self.pid = pid
        self.arrival_time = arrival_time
        self.burst_time = burst_time
        self.waiting_time = 0
        self.turnaround_time = 0


def fcfs(processes):
    processes.sort(key=lambda p: p.arrival_time)
    total_waiting_time = 0
    total_turnaround_time = 0
    current_time = 0
    for process in processes:
        if current_time < process.arrival_time:
            current_time = process.arrival_time
        process.waiting_time = current_time - process.arrival_time
        process.turnaround_time = process.waiting_time + process.burst_time
        current_time += process.burst_time
        total_waiting_time += process.waiting_time
        total_turnaround_time += process.turnaround_time
    avg_waiting_time = total_waiting_time / len(processes)
    avg_turnaround_time = total_turnaround_time / len(processes)
    return avg_waiting_time, avg_turnaround_time


if __name__ == "__main__":
    processes = [
        Process(1, 0, 24),
        Process(2, 0, 3),
        Process(3, 0, 3)
    ]
    avg_wait, avg_turn = fcfs(processes)
    print(f"平均等待时间: {avg_wait}")
    print(f"平均周转时间: {avg_turn}")

从公平性角度来看,FCFS 在某种程度上是公平的,因为它对所有进程一视同仁,按照到达顺序分配资源。然而,这种策略存在明显的公平性缺陷。如果一个长作业先到达,后续的短作业就必须等待很长时间,这可能导致短作业的响应时间过长,从而降低了系统的交互性。这种不公平性在实际应用中可能会严重影响用户体验,特别是对于交互式系统。

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

SJF 调度策略旨在优先调度那些预计执行时间最短的进程。它的目标是最小化平均周转时间,从而提高系统的整体效率。SJF 分为非抢占式和抢占式两种。

非抢占式 SJF 在一个进程开始执行后,直到它完成或者主动放弃 CPU 才会调度其他进程。而抢占式 SJF(也称为最短剩余时间优先,SRTF)则会在有更短剩余时间的进程到达时,立即抢占当前正在执行的进程的 CPU 资源。

以下是一个简单的 Python 代码示例,模拟非抢占式 SJF 调度算法:

class Process:
    def __init__(self, pid, arrival_time, burst_time):
        self.pid = pid
        self.arrival_time = arrival_time
        self.burst_time = burst_time
        self.waiting_time = 0
        self.turnaround_time = 0


def sjf_non_preemptive(processes):
    processes.sort(key=lambda p: (p.arrival_time, p.burst_time))
    total_waiting_time = 0
    total_turnaround_time = 0
    current_time = 0
    completed = 0
    while completed < len(processes):
        eligible_processes = [p for p in processes if p.arrival_time <= current_time and not hasattr(p, 'finished')]
        if not eligible_processes:
            current_time += 1
            continue
        next_process = min(eligible_processes, key=lambda p: p.burst_time)
        next_process.waiting_time = current_time - next_process.arrival_time
        next_process.turnaround_time = next_process.waiting_time + next_process.burst_time
        current_time += next_process.burst_time
        setattr(next_process, 'finished', True)
        completed += 1
        total_waiting_time += next_process.waiting_time
        total_turnaround_time += next_process.turnaround_time
    avg_waiting_time = total_waiting_time / len(processes)
    avg_turnaround_time = total_turnaround_time / len(processes)
    return avg_waiting_time, avg_turnaround_time


if __name__ == "__main__":
    processes = [
        Process(1, 0, 6),
        Process(2, 0, 8),
        Process(3, 0, 7),
        Process(4, 0, 3)
    ]
    avg_wait, avg_turn = sjf_non_preemptive(processes)
    print(f"平均等待时间: {avg_wait}")
    print(f"平均周转时间: {avg_turn}")

SJF 在公平性方面有其独特之处。它确实能够有效地减少平均周转时间,对于短作业来说是比较公平的,因为它们能够快速得到执行。然而,对于长作业而言,SJF 可能是不公平的。如果不断有短作业到达,长作业可能会被无限期延迟,导致饥饿现象。这种不公平性在实际系统中同样会带来问题,特别是对于一些需要长时间运行但又很重要的任务。

时间片轮转(RR, Round - Robin)

时间片轮转调度策略为每个进程分配一个固定长度的时间片(也称为时间量子)。当进程的时间片用完后,即使该进程尚未完成,它也会被抢占,然后被重新放入就绪队列的末尾,等待下一次调度。

以下是一个简单的 Python 代码示例,模拟时间片轮转调度算法:

class Process:
    def __init__(self, pid, burst_time):
        self.pid = pid
        self.burst_time = burst_time
        self.remaining_time = burst_time
        self.waiting_time = 0


def round_robin(processes, time_quantum):
    total_waiting_time = 0
    current_time = 0
    queue = processes.copy()
    while queue:
        process = queue.pop(0)
        if process.remaining_time <= time_quantum:
            current_time += process.remaining_time
            process.remaining_time = 0
            total_waiting_time += current_time - process.burst_time
        else:
            current_time += time_quantum
            process.remaining_time -= time_quantum
            queue.append(process)
    avg_waiting_time = total_waiting_time / len(processes)
    return avg_waiting_time


if __name__ == "__main__":
    processes = [
        Process(1, 24),
        Process(2, 3),
        Process(3, 3)
    ]
    time_quantum = 4
    avg_wait = round_robin(processes, time_quantum)
    print(f"平均等待时间: {avg_wait}")

RR 的公平性体现在它确保每个进程都能定期获得 CPU 时间,不会出现某个进程长时间得不到执行的情况。这对于交互式进程尤为重要,因为它能够保证及时响应。然而,RR 的公平性也依赖于时间片的选择。如果时间片过长,RR 就会退化为 FCFS,长作业会占据过多的 CPU 时间,对短作业不公平;如果时间片过短,进程上下文切换的开销会增大,导致系统性能下降,从整体资源利用的角度看,这也是一种不公平。

优先级调度(Priority Scheduling)

优先级调度策略根据进程的优先级来分配 CPU 资源。优先级高的进程优先执行。优先级可以根据进程的类型(如系统进程优先级高于用户进程)、进程的紧急程度等因素来确定。

优先级调度同样分为非抢占式和抢占式。非抢占式优先级调度在一个进程开始执行后,只有当它完成或者主动放弃 CPU 时,才会调度更高优先级的进程。而抢占式优先级调度则会在有更高优先级的进程到达时,立即抢占当前正在执行进程的 CPU 资源。

以下是一个简单的 Python 代码示例,模拟非抢占式优先级调度算法:

class Process:
    def __init__(self, pid, arrival_time, burst_time, priority):
        self.pid = pid
        self.arrival_time = arrival_time
        self.burst_time = burst_time
        self.priority = priority
        self.waiting_time = 0
        self.turnaround_time = 0


def priority_non_preemptive(processes):
    processes.sort(key=lambda p: (p.arrival_time, p.priority))
    total_waiting_time = 0
    total_turnaround_time = 0
    current_time = 0
    completed = 0
    while completed < len(processes):
        eligible_processes = [p for p in processes if p.arrival_time <= current_time and not hasattr(p, 'finished')]
        if not eligible_processes:
            current_time += 1
            continue
        next_process = min(eligible_processes, key=lambda p: p.priority)
        next_process.waiting_time = current_time - next_process.arrival_time
        next_process.turnaround_time = next_process.waiting_time + next_process.burst_time
        current_time += next_process.burst_time
        setattr(next_process, 'finished', True)
        completed += 1
        total_waiting_time += next_process.waiting_time
        total_turnaround_time += next_process.turnaround_time
    avg_waiting_time = total_waiting_time / len(processes)
    avg_turnaround_time = total_turnaround_time / len(processes)
    return avg_waiting_time, avg_turnaround_time


if __name__ == "__main__":
    processes = [
        Process(1, 0, 10, 3),
        Process(2, 0, 1, 1),
        Process(3, 0, 2, 4),
        Process(4, 0, 1, 5),
        Process(5, 0, 5, 2)
    ]
    avg_wait, avg_turn = priority_non_preemptive(processes)
    print(f"平均等待时间: {avg_wait}")
    print(f"平均周转时间: {avg_turn}")

从公平性角度分析,优先级调度在满足系统对不同类型进程的需求方面有一定优势。例如,对于实时进程,可以赋予较高优先级以保证其及时执行。然而,如果优先级的分配不合理,低优先级进程可能会出现饥饿现象,导致公平性受损。而且,如何准确地确定进程的优先级是一个复杂的问题,不当的优先级设置可能会引发一系列不公平的情况。

公平性在现代操作系统进程调度策略中的改进

多级反馈队列调度(Multilevel Feedback Queue Scheduling)

多级反馈队列调度结合了多种调度策略的优点,旨在更好地实现公平性。它维护多个就绪队列,每个队列具有不同的优先级和时间片。新进程首先进入最高优先级队列,按照时间片轮转方式执行。如果在一个时间片内没有完成,该进程会被移到下一级队列。下一级队列的时间片通常会比上一级队列的时间片长。

这样的设计使得短作业能够在高优先级队列中快速完成,而长作业随着逐渐降级,也能获得相对较多的 CPU 时间,避免了饥饿现象。同时,对于交互式进程,由于它们通常能在短时间内完成操作,所以能够在高优先级队列中得到及时响应,提高了系统的交互性和公平性。

以下是一个简单的 Python 代码示例,模拟多级反馈队列调度算法:

class Process:
    def __init__(self, pid, burst_time):
        self.pid = pid
        self.burst_time = burst_time
        self.remaining_time = burst_time
        self.waiting_time = 0


def multilevel_feedback_queue(processes, time_quantums):
    total_waiting_time = 0
    current_time = 0
    queues = [[] for _ in range(len(time_quantums))]
    queues[0] = processes.copy()
    while any(queues):
        for i, queue in enumerate(queues):
            while queue:
                process = queue.pop(0)
                if process.remaining_time <= time_quantums[i]:
                    current_time += process.remaining_time
                    process.remaining_time = 0
                    total_waiting_time += current_time - process.burst_time
                else:
                    current_time += time_quantums[i]
                    process.remaining_time -= time_quantums[i]
                    if i < len(queues) - 1:
                        queues[i + 1].append(process)
                    else:
                        queues[-1].append(process)
    avg_waiting_time = total_waiting_time / len(processes)
    return avg_waiting_time


if __name__ == "__main__":
    processes = [
        Process(1, 24),
        Process(2, 3),
        Process(3, 3)
    ]
    time_quantums = [4, 8]
    avg_wait = multilevel_feedback_queue(processes, time_quantums)
    print(f"平均等待时间: {avg_wait}")

公平共享调度(Fair - Share Scheduling)

公平共享调度的核心思想是将 CPU 资源按照某种公平的方式分配给不同的用户、组或者进程集合。例如,系统可以根据用户拥有的进程数量或者用户的权重来分配 CPU 时间。这样,即使某个用户有多个进程在运行,每个进程也能获得相对公平的 CPU 时间份额,而不是某个用户的进程占据过多资源。

在实际实现中,公平共享调度可以基于彩票调度(Lottery Scheduling)等算法。彩票调度为每个进程分配一定数量的彩票,当进行调度时,通过随机抽取彩票的方式决定哪个进程获得 CPU 资源。进程拥有的彩票数量越多,被选中的概率就越大。这种方式从概率角度实现了公平性,每个进程都有机会获得 CPU 资源,而且资源分配比例与进程的“彩票权重”相关。

以下是一个简单的 Python 代码示例,模拟彩票调度算法:

import random


class Process:
    def __init__(self, pid, burst_time, lottery_count):
        self.pid = pid
        self.burst_time = burst_time
        self.lottery_count = lottery_count
        self.remaining_time = burst_time


def lottery_scheduling(processes):
    total_lottery_count = sum(p.lottery_count for p in processes)
    current_time = 0
    while any(p.remaining_time > 0 for p in processes):
        lottery_winner = random.randint(1, total_lottery_count)
        cumulative_count = 0
        for process in processes:
            cumulative_count += process.lottery_count
            if lottery_winner <= cumulative_count:
                process.remaining_time -= 1
                current_time += 1
                if process.remaining_time == 0:
                    total_lottery_count -= process.lottery_count
                break


if __name__ == "__main__":
    processes = [
        Process(1, 5, 2),
        Process(2, 3, 3),
        Process(3, 4, 5)
    ]
    lottery_scheduling(processes)

基于公平性的实时调度策略

在实时系统中,公平性同样重要。实时进程通常有严格的时间限制,需要保证它们能够按时完成任务。实时调度策略如最早截止时间优先(EDF, Earliest Deadline First)和速率单调调度(RMS, Rate - Monotonic Scheduling)在保证实时任务的及时性的同时,也考虑了公平性。

EDF 根据任务的截止时间来调度,截止时间越早的任务越优先执行。在多个实时任务竞争 CPU 资源时,EDF 能够确保每个任务都有机会在其截止时间前完成,从这个角度体现了公平性。RMS 则是基于任务的周期来分配优先级,周期越短的任务优先级越高。通过合理分配优先级,RMS 也能在一定程度上保证实时任务的公平执行。

以下是一个简单的 Python 代码示例,模拟 EDF 调度算法:

class Process:
    def __init__(self, pid, burst_time, deadline):
        self.pid = pid
        self.burst_time = burst_time
        self.deadline = deadline
        self.remaining_time = burst_time


def edf(processes):
    current_time = 0
    while any(p.remaining_time > 0 for p in processes):
        eligible_processes = [p for p in processes if p.remaining_time > 0]
        next_process = min(eligible_processes, key=lambda p: p.deadline)
        next_process.remaining_time -= 1
        current_time += 1
        if next_process.remaining_time == 0:
            if current_time > next_process.deadline:
                print(f"进程 {next_process.pid} 错过截止时间")


if __name__ == "__main__":
    processes = [
        Process(1, 3, 5),
        Process(2, 2, 4),
        Process(3, 4, 8)
    ]
    edf(processes)

公平性对系统性能和用户体验的影响

对系统性能的影响

公平的进程调度策略能够提高系统资源的利用率。当每个进程都能公平地获得 CPU 时间时,系统可以避免资源的浪费。例如,在 SJF 中,如果长作业长时间得不到执行,它们占用的内存等资源就处于闲置状态,而公平的调度策略可以让这些资源得到更充分的利用。

同时,公平性有助于平衡系统的负载。不同类型的进程(如计算密集型和 I/O 密集型)在公平的调度环境下,能够更好地协调资源的使用。计算密集型进程可能需要更多的 CPU 时间,而 I/O 密集型进程在等待 I/O 操作完成时,CPU 可以分配给其他进程,这样可以提高系统整体的吞吐量。

对用户体验的影响

在交互式系统中,公平性直接关系到用户体验。如果调度策略不公平,交互式进程可能会因为等待时间过长而导致响应迟缓,用户会感觉到系统卡顿。例如,在一个多任务的操作系统中,如果后台的批处理作业占据了过多的 CPU 时间,前台的用户操作(如鼠标点击、键盘输入等对应的进程)就无法及时得到处理,用户会对系统的响应速度产生不满。

而公平的调度策略能够保证交互式进程及时获得 CPU 资源,从而提供流畅的用户体验。对于服务器系统,公平性也很重要,它可以确保不同用户的请求都能得到合理的处理,避免某些用户的服务质量受到严重影响。

公平性实现中的挑战与未来发展

公平性实现的挑战

准确衡量公平性是一个挑战。不同的应用场景和用户需求对公平性的定义可能不同。例如,在多媒体处理系统中,可能更注重实时进程的公平性,而在通用办公系统中,可能更强调用户交互进程的公平性。如何制定一个通用且准确的公平性衡量标准是一个难题。

另外,实现公平性往往需要额外的系统开销。例如,多级反馈队列调度需要维护多个队列,并且在进程在队列间移动时需要进行额外的管理操作;公平共享调度中的彩票调度需要进行随机数生成等操作,这些都会增加系统的计算负担。在保证公平性的同时,如何平衡系统开销也是一个需要解决的问题。

未来发展方向

随着计算机系统架构的不断发展,如多核处理器的广泛应用,进程调度策略中的公平性也需要不断演进。未来的调度策略可能需要更好地利用多核处理器的并行性,在多个核心上实现公平的资源分配。例如,可以针对不同核心制定不同的公平调度策略,或者将进程动态地分配到最合适的核心上以实现公平性和性能的平衡。

同时,人工智能和机器学习技术在进程调度中的应用也为公平性的提升带来了新的机遇。通过对进程行为的学习和预测,系统可以更智能地分配 CPU 资源,实现更加精准的公平调度。例如,机器学习算法可以根据进程的历史执行数据预测其未来的资源需求,从而为其分配更合理的 CPU 时间份额,进一步优化公平性。