进程调度算法对比:在 Python 中实现最短作业优先(SJF)和时间片轮转(RR)进程调度算法,设置多个进程的相关参数(如到达时间、执行时间、时间片大小等),分别模拟这两种算法的调度过程,计算并比较它们的性能指标(如平均等待时间、平均周转时间等),分析不同算法在不同场景下的优缺点。
import matplotlib.pyplot as plt import numpy as np from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import tkinter as tk from tkinter import ttk, scrolledtext from matplotlib.font_manager import FontProperties # 设置中文字体 try: font = FontProperties(fname=r"C:\Windows\Fonts\simhei.ttf") # Windows系统默认黑体 except: try: font = FontProperties(fname=r"/System/Library/Fonts/PingFang.ttc") # macOS系统 except: try: font = FontProperties(fname=r"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc") # Linux系统 except: font = None # 如果找不到中文字体,使用默认字体 class Process: def __init__(self, pid, arrival_time, burst_time): self.pid = pid # 进程ID self.arrival_time = arrival_time # 到达时间 self.burst_time = burst_time # 执行时间 self.remaining_time = burst_time # 剩余执行时间 self.start_time = None # 首次执行时间 self.completion_time = None # 完成时间 self.waiting_time = 0 # 等待时间 self.turnaround_time = 0 # 周转时间 def sjf_scheduling(processes): """实现最短作业优先(SJF)调度算法""" # 按到达时间排序 processes.sort(key=lambda x: x.arrival_time) n = len(processes) current_time = 0 completed = 0 ready_queue = [] scheduled_order = [] # 记录调度顺序 time_points = [] # 记录时间点 while completed != n: # 将已到达的进程加入就绪队列 for process in processes: if process.arrival_time <= current_time and process not in ready_queue and process.completion_time is None: ready_queue.append(process) # 按剩余执行时间排序(SJF的核心) ready_queue.sort(key=lambda x: x.remaining_time) if not ready_queue: current_time += 1 continue # 获取当前要执行的进程 current_process = ready_queue.pop(0) # 记录调度信息 if current_process.start_time is None: current_process.start_time = current_time # 执行该进程直到完成 current_time += current_process.remaining_time current_process.remaining_time = 0 current_process.completion_time = current_time current_process.turnaround_time = current_process.completion_time - current_process.arrival_time current_process.waiting_time = current_process.turnaround_time - current_process.burst_time # 记录调度顺序和时间点 scheduled_order.append(current_process.pid) time_points.append(current_time) completed += 1 # 计算平均指标 avg_waiting_time = sum(p.waiting_time for p in processes) / n avg_turnaround_time = sum(p.turnaround_time for p in processes) / n return processes, avg_waiting_time, avg_turnaround_time, scheduled_order, time_points def rr_scheduling(processes, time_quantum): """实现时间片轮转(RR)调度算法""" # 按到达时间排序 processes = sorted(processes, key=lambda x: x.arrival_time) n = len(processes) current_time = 0 completed = 0 ready_queue = [] temp_processes = [Process(p.pid, p.arrival_time, p.burst_time) for p in processes] scheduled_order = [] # 记录调度顺序 time_points = [] # 记录时间点 time_quantum_points = [] # 记录时间片切换点 while completed != n: # 将已到达的进程加入就绪队列 for process in temp_processes: if process.arrival_time <= current_time and process not in ready_queue and process.remaining_time > 0: ready_queue.append(process) if not ready_queue: current_time += 1 continue # 获取当前要执行的进程(FIFO顺序) current_process = ready_queue.pop(0) # 记录调度信息 if current_process.start_time is None: current_process.start_time = current_time # 执行一个时间片或直到进程完成 execution_time = min(time_quantum, current_process.remaining_time) current_process.remaining_time -= execution_time current_time += execution_time # 记录调度顺序和时间点 scheduled_order.append(current_process.pid) time_points.append(current_time) time_quantum_points.append(current_time) # 检查进程是否完成 if current_process.remaining_time == 0: current_process.completion_time = current_time current_process.turnaround_time = current_process.completion_time - current_process.arrival_time current_process.waiting_time = current_process.turnaround_time - current_process.burst_time completed += 1 else: # 未完成则重新加入就绪队列 current_process.arrival_time = current_time # 更新到达时间为当前时间 ready_queue.append(current_process) # 计算平均指标 avg_waiting_time = sum(p.waiting_time for p in temp_processes) / n avg_turnaround_time = sum(p.turnaround_time for p in temp_processes) / n return temp_processes, avg_waiting_time, avg_turnaround_time, scheduled_order, time_points, time_quantum_points class ProcessSchedulingApp: def __init__(self, root): self.root = root self.root.title("进程调度算法可视化") self.root.geometry("900x700") # 设置中文字体 self.font = ('SimHei', 10) if font else ('Arial', 10) # 初始化时间片大小 self.time_quantum = 2 # 默认时间片大小 # 初始化示例进程 self.processes = [ Process(1, 0, 5), Process(2, 1, 3), Process(3, 2, 8), Process(4, 3, 6) ] # 创建主框架 self.main_frame = ttk.Frame(root, padding="10") self.main_frame.pack(fill=tk.BOTH, expand=True) # 标题 ttk.Label(self.main_frame, text="进程调度算法对比", font=('SimHei', 16, 'bold')).pack(pady=10) # 进程参数设置 self.setup_process_frame() # 按钮框架 btn_frame = ttk.Frame(self.main_frame) btn_frame.pack(fill=tk.X, pady=10) ttk.Button(btn_frame, text="运行模拟", command=self.run_simulation).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="显示算法分析", command=self.show_algorithm_analysis).pack(side=tk.LEFT, padx=5) # 结果框架 self.result_frame = ttk.Frame(self.main_frame) self.result_frame.pack(fill=tk.BOTH, expand=True, pady=10) # 初始化结果文本区域 self.result_text = scrolledtext.ScrolledText(self.result_frame, wrap=tk.WORD, height=15) self.result_text.pack(fill=tk.BOTH, expand=True) # 初始化图表 self.fig, (self.ax1, self.ax2) = plt.subplots(2, 1, figsize=(10, 8)) self.canvas = FigureCanvasTkAgg(self.fig, master=self.result_frame) self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) def setup_process_frame(self): """设置进程参数输入框架""" process_frame = ttk.LabelFrame(self.main_frame, text="进程参数设置", padding="10") process_frame.pack(fill=tk.X, pady=10) # 时间片设置 ttk.Label(process_frame, text="时间片大小:", font=self.font).grid(row=0, column=0, sticky=tk.W, pady=5) self.time_quantum_var = tk.IntVar(value=self.time_quantum) ttk.Entry(process_frame, textvariable=self.time_quantum_var, width=5).grid(row=0, column=1, sticky=tk.W, pady=5) # 进程列表 ttk.Label(process_frame, text="进程列表:", font=self.font).grid(row=1, column=0, sticky=tk.W, pady=5) self.process_entries = [] for i, p in enumerate(self.processes): frame = ttk.Frame(process_frame) frame.grid(row=i + 2, column=0, columnspan=3, sticky=tk.W, pady=2) ttk.Label(frame, text=f"进程 {i + 1}:", width=8, font=self.font).pack(side=tk.LEFT, padx=2) arrival_var = tk.IntVar(value=p.arrival_time) ttk.Label(frame, text="到达时间:", width=8, font=self.font).pack(side=tk.LEFT, padx=2) ttk.Entry(frame, textvariable=arrival_var, width=5).pack(side=tk.LEFT, padx=2) burst_var = tk.IntVar(value=p.burst_time) ttk.Label(frame, text="执行时间:", width=8, font=self.font).pack(side=tk.LEFT, padx=2) ttk.Entry(frame, textvariable=burst_var, width=5).pack(side=tk.LEFT, padx=2) self.process_entries.append((arrival_var, burst_var)) def get_processes_from_entries(self): """从输入框获取进程参数""" processes = [] for i, (arrival_var, burst_var) in enumerate(self.process_entries): pid = i + 1 arrival_time = arrival_var.get() burst_time = burst_var.get() processes.append(Process(pid, arrival_time, burst_time)) return processes def run_simulation(self): """运行调度模拟并显示结果""" # 获取参数 self.processes = self.get_processes_from_entries() self.time_quantum = self.time_quantum_var.get() # 复制进程列表以避免修改原始数据 sjf_processes = [Process(p.pid, p.arrival_time, p.burst_time) for p in self.processes] rr_processes = [Process(p.pid, p.arrival_time, p.burst_time) for p in self.processes] # 执行SJF调度 sjf_result, sjf_avg_wait, sjf_avg_turn, sjf_order, sjf_times = sjf_scheduling(sjf_processes) # 执行RR调度 rr_result, rr_avg_wait, rr_avg_turn, rr_order, rr_times, rr_quantum_points = rr_scheduling(rr_processes, self.time_quantum) # 保存结果供分析使用 self.sjf_avg_wait = sjf_avg_wait self.sjf_avg_turn = sjf_avg_turn self.rr_avg_wait = rr_avg_wait self.rr_avg_turn = rr_avg_turn # 显示结果 self.result_text.delete(1.0, tk.END) self.result_text.insert(tk.END, "\n===== 最短作业优先(SJF)调度 =====\n") self.result_text.insert(tk.END, "进程\t到达时间\t执行时间\t完成时间\t周转时间\t等待时间\n") for p in sjf_result: self.result_text.insert(tk.END, f"P{p.pid}\t{p.arrival_time}\t\t{p.burst_time}\t\t{p.completion_time}\t\t{p.turnaround_time}\t\t{p.waiting_time}\n") self.result_text.insert(tk.END, f"平均等待时间: {sjf_avg_wait:.2f}\n") self.result_text.insert(tk.END, f"平均周转时间: {sjf_avg_turn:.2f}\n\n") self.result_text.insert(tk.END, "===== 时间片轮转(RR)调度 =====\n") self.result_text.insert(tk.END, f"时间片大小: {self.time_quantum}\n") self.result_text.insert(tk.END, "进程\t到达时间\t执行时间\t完成时间\t周转时间\t等待时间\n") for p in rr_result: self.result_text.insert(tk.END, f"P{p.pid}\t{p.arrival_time}\t\t{p.burst_time}\t\t{p.completion_time}\t\t{p.turnaround_time}\t\t{p.waiting_time}\n") self.result_text.insert(tk.END, f"平均等待时间: {rr_avg_wait:.2f}\n") self.result_text.insert(tk.END, f"平均周转时间: {rr_avg_turn:.2f}\n") # 更新图表 self.update_charts(sjf_order, sjf_times, rr_order, rr_times, rr_quantum_points, sjf_avg_wait, sjf_avg_turn, rr_avg_wait, rr_avg_turn) def update_charts(self, sjf_order, sjf_times, rr_order, rr_times, rr_quantum_points, sjf_avg_wait, sjf_avg_turn, rr_avg_wait, rr_avg_turn): """更新图表显示""" # 清除现有图表 self.ax1.clear() self.ax2.clear() # SJF甘特图 self.ax1.set_title('最短作业优先(SJF)调度', fontproperties=font) self.ax1.set_xlabel('时间', fontproperties=font) self.ax1.set_ylabel('进程', fontproperties=font) prev_time = 0 y_pos = 0.5 colors = plt.cm.tab20(np.linspace(0, 1, len(set(sjf_order)))) for i, (pid, time) in enumerate(zip(sjf_order, sjf_times)): duration = time - prev_time color_idx = list(set(sjf_order)).index(pid) self.ax1.barh(y_pos, duration, left=prev_time, height=0.6, color=colors[color_idx], edgecolor='black', alpha=0.8) self.ax1.text(prev_time + duration / 2, y_pos, f'P{pid}', ha='center', va='center', fontproperties=font) prev_time = time self.ax1.set_ylim(0, 1) self.ax1.set_xlim(0, max(sjf_times)) self.ax1.set_yticks([]) self.ax1.grid(axis='x', linestyle='--', alpha=0.7) # 添加平均时间信息 self.ax1.text(0.02, 0.9, f'平均等待时间: {sjf_avg_wait:.2f}', transform=self.ax1.transAxes, fontproperties=font) self.ax1.text(0.02, 0.8, f'平均周转时间: {sjf_avg_turn:.2f}', transform=self.ax1.transAxes, fontproperties=font) # RR甘特图 self.ax2.set_title(f'时间片轮转(RR)调度 (时间片={self.time_quantum})', fontproperties=font) self.ax2.set_xlabel('时间', fontproperties=font) self.ax2.set_ylabel('进程', fontproperties=font) prev_time = 0 y_pos = 0.5 for i, (pid, time) in enumerate(zip(rr_order, rr_times)): duration = time - prev_time color_idx = list(set(rr_order)).index(pid) self.ax2.barh(y_pos, duration, left=prev_time, height=0.6, color=colors[color_idx], edgecolor='black', alpha=0.8) self.ax2.text(prev_time + duration / 2, y_pos, f'P{pid}', ha='center', va='center', fontproperties=font) prev_time = time # 标记时间片切换点 for t in rr_quantum_points[:-1]: self.ax2.axvline(x=t, color='red', linestyle='--', alpha=0.5) self.ax2.set_ylim(0, 1) self.ax2.set_xlim(0, max(rr_times)) self.ax2.set_yticks([]) self.ax2.grid(axis='x', linestyle='--', alpha=0.7) # 添加平均时间信息 self.ax2.text(0.02, 0.9, f'平均等待时间: {rr_avg_wait:.2f}', transform=self.ax2.transAxes, fontproperties=font) self.ax2.text(0.02, 0.8, f'平均周转时间: {rr_avg_turn:.2f}', transform=self.ax2.transAxes, fontproperties=font) # 调整布局并绘制 self.fig.tight_layout() self.canvas.draw() def show_algorithm_analysis(self): """显示算法分析窗口""" if not hasattr(self, 'sjf_avg_wait'): # 如果还没有运行模拟,先运行模拟 self.run_simulation() analysis_window = tk.Toplevel(self.root) analysis_window.title("算法分析") analysis_window.geometry("600x500") frame = ttk.Frame(analysis_window, padding="10") frame.pack(fill=tk.BOTH, expand=True) # 创建文本区域 text_widget = scrolledtext.ScrolledText(frame, wrap=tk.WORD, font=self.font) text_widget.pack(fill=tk.BOTH, expand=True) # 添加算法分析内容 analysis_text = """ 进程调度算法分析 1. 最短作业优先(SJF)算法 优点: - 平均等待时间和周转时间通常最优,能有效提高系统吞吐量 - 短作业能够快速完成,减少了整体响应时间 - 在作业执行时间差异较大的情况下表现尤为出色 缺点: - 需要预先知道作业执行时间,这在实际系统中往往难以实现 - 对长作业不利,可能导致长作业长时间得不到处理(饥饿现象) - 无法适应动态到达的作业流 适用场景: - 批处理系统,作业执行时间可预测的场景 - 作业执行时间差异较大的环境 - 对系统吞吐量要求较高的场景 2. 时间片轮转(RR)算法 优点: - 公平性好,每个进程都能在一定时间内得到处理 - 响应时间快,适合交互式系统,用户体验较好 - 实现简单,不需要预先知道作业执行时间 缺点: - 时间片设置过小将增加上下文切换开销,降低系统效率 - 时间片设置过大则退化为FCFS算法,失去RR的优势 - 平均周转时间通常比SJF长 适用场景: - 分时系统,需要快速响应的交互式系统 - 作业执行时间相近的环境 - 对公平性要求较高的场景 3. 性能对比分析 在本次模拟中: - SJF算法的平均等待时间为 {sjf_avg_wait:.2f} - RR算法的平均等待时间为 {rr_avg_wait:.2f} - SJF算法的平均周转时间为 {sjf_avg_turn:.2f} - RR算法的平均周转时间为 {rr_avg_turn:.2f} 结论: - 如果作业执行时间可预测,且追求最小的平均等待时间和周转时间,SJF是更好的选择 - 如果系统需要快速响应,保证公平性,RR是更合适的算法 - 时间片大小对RR算法的性能影响很大,需要根据系统特性合理设置 选择调度算法时,需要根据系统类型、作业特性和性能目标综合考虑。 """.format( sjf_avg_wait=self.sjf_avg_wait, rr_avg_wait=self.rr_avg_wait, sjf_avg_turn=self.sjf_avg_turn, rr_avg_turn=self.rr_avg_turn ) text_widget.insert(tk.END, analysis_text) text_widget.config(state=tk.DISABLED) # 设为只读 # 添加关闭按钮 ttk.Button(frame, text="关闭", command=analysis_window.destroy).pack(pady=10) def main(): root = tk.Tk() app = ProcessSchedulingApp(root) root.mainloop() if __name__ == "__main__": main()