调度基础:批处理、分时与实时系统
你有没有想过一个问题:你的笔记本同时开着浏览器、IDE、音乐播放器、十几个后台进程,CPU 核心却只有几个。它们凭什么看起来都在"同时"运行?答案是调度(scheduling)——操作系统在毫秒甚至微秒尺度上飞快地在这些任务之间切换 CPU,快到你的感官无法察觉,于是产生了"并行"的错觉。
很多人学调度,一上来就背 FCFS、SJF、RR 这些算法。但这是本末倒置。算法只是手段,调度真正的起点是目标——这个系统到底想优化什么?是让一批任务整体跑得最快,还是让每个用户感觉响应最及时,还是让某个任务绝对不能错过截止时间?目标不同,"好调度"的定义就完全不同,算法的选择自然天差地别。
这一篇我们先讲清调度器的运转机制(它在何时、依据什么做决策、用哪些指标衡量好坏),再说明为什么批处理、分时、实时三类系统会对调度提出截然不同的要求。先有机制和目标,下一篇的具体算法才有评判的标尺。
一、调度器到底在做什么决策
把调度拆到最朴素的形态:系统里有一堆**就绪(ready)**的任务(它们都准备好了、只差 CPU),而 CPU 资源有限。调度器要回答两个问题:
- 选谁:下一个该把 CPU 给哪个就绪任务?
- 给多久:让它一直跑到主动让出,还是到点强制收回?
这两个决策合起来,就是调度策略的全部。为了支撑它,每个进程在内核里都有一个进程控制块(PCB),记录它的状态、优先级、已运行时间等信息;所有就绪任务挂在就绪队列里。调度器做的事,就是按某种规则从就绪队列里挑一个,把 CPU 交给它(这个交接动作叫上下文切换)。
调度的本质是一个反复进行的选择:在"谁该用 CPU"和"用多久"之间,依据某种策略做出决定。所有调度算法的差异,都落在这两个问题的答案上。
二、关键分水岭:抢占式 vs 非抢占式
"给多久"这个问题,引出了调度最重要的一条分界线。
调度决策的时机其实只有四个:①任务从运行态转入阻塞态(比如发起 IO 要等);②任务运行结束退出;③任务从阻塞态被唤醒回到就绪态;④时钟中断到来(一个时间片用完)。
- 如果调度器只在 ① 和 ② 发生时才重新选任务——也就是只有当前任务自己阻塞或结束了才换人——这叫**非抢占式(non-preemptive)**调度。当前任务一旦拿到 CPU,就能一直跑到自己主动让出。
- 如果调度器在 ③ ④ 时也能介入,强行把正在跑的任务踢下来换别人——这叫**抢占式(preemptive)**调度。任务跑着跑着可能被时钟中断打断、被更高优先级的新任务挤掉。
抢占式与非抢占式的区别,决定了一个任务能不能"霸占"CPU。非抢占简单、切换少,但一个长任务会让所有人干等;抢占复杂、切换多,但能保证响应性和公平性。现代通用系统几乎全是抢占式的——你正是靠它才能在大计算跑着时还能拖动鼠标。
这条分水岭直接对应后面三类系统的取舍:批处理偏好非抢占(少切换、高吞吐),分时和实时必须抢占(保响应、保截止时间)。
三、衡量调度好坏的指标
要比较"哪个调度更好",得先有度量。几个核心指标,请务必分清,因为它们经常互相冲突:
- 吞吐量(throughput):单位时间内完成的任务数。越高越好,批处理最看重它。
- 周转时间(turnaround time):从任务提交到完成的总时长(包含排队等待 + 实际运行)。越短越好。
- 等待时间(waiting time):任务在就绪队列里干等、没拿到 CPU 的总时长。它是周转时间里最能被调度优化的部分。
- 响应时间(response time):从任务提交到第一次开始响应(产生第一个输出)的时长。注意它不等于周转时间——交互系统里你按下键,关心的是"多快有反应",而不是"整件事多久全干完"。分时系统最看重它。
- 公平性(fairness):每个任务/用户是否得到了合理份额的 CPU,有没有谁被长期饿着。
- 可预测性 / 确定性:最坏情况下的延迟是否有保证。实时系统的命根子。
这些指标常常互相打架:追求吞吐量可能牺牲响应时间,追求公平可能降低吞吐,追求确定性往往要牺牲平均性能。这正是调度无法"全都要"、必须做取舍的根本原因——也是下面三类系统分道扬镳的起点。
四、批处理系统:一切为了吞吐量
批处理(batch)系统是最早的形态:任务(作业)被成批提交,没有用户坐在屏幕前实时交互,系统在后台一个接一个地处理。典型如夜间跑的财务结算、大规模数据处理、离线报表生成。
既然没人盯着等即时反馈,批处理系统的核心目标就非常明确:最大化吞吐量、最小化平均周转时间,把昂贵的 CPU 喂得尽可能饱。
这决定了它的调度偏好:
- 倾向非抢占式:减少上下文切换的开销,让任务尽量跑完再换,把 CPU 时间花在"干活"而非"切换"上;
- 偏好短任务优先:先跑短的能显著降低平均周转时间(下一篇 SJF 会证明这点);
- 不在乎响应时间:反正没人等第一个输出,只关心整批什么时候全部完成。
批处理的世界里,“快"指的是"整批活儿干完得快”,而不是"某一个任务反应得快"。响应时间这个指标在这里几乎没有意义。
五、分时系统:一切为了响应
分时(time-sharing)系统是为"多个用户/任务交互式共享一台机器"而生的。你现在用的 Linux、macOS、Windows 桌面,本质都是分时系统。多个用户(或多个交互进程)仿佛各自独占了一台机器,其实是 CPU 在它们之间飞快轮转。
它的核心目标变了:保证良好的响应时间和公平性——让每个交互任务都能很快得到反应,没有谁被冷落太久。
这决定了分时系统的调度机制:
- 必须抢占式:靠**时间片(time slice / quantum)**实现。每个任务最多连续跑一个时间片(通常几毫秒到几十毫秒),时间片一到,时钟中断触发,调度器强制换下一个。这样任何任务都不可能长期霸占 CPU;
- 重视响应时间甚于周转时间:你敲一个命令,系统几十毫秒内有反应就行,至于这个命令背后整个任务多久跑完,你感知不强;
- 追求公平:每个任务轮流获得时间片,大致均分 CPU。
时间片的长短是个关键权衡:太长,轮转一圈慢、交互变卡;太短,上下文切换太频繁、开销吃掉有效计算。这个权衡下一篇讲 RR 算法时会展开。
分时系统用"时间片 + 抢占"把 CPU 切成均匀的小份轮流分配,牺牲了一点吞吐量(切换有开销),换来了人机交互所必需的响应性和公平。这是一笔非常划算的买卖——对人来说,"等得烦"比"慢一点"更不可接受。
六、实时系统:一切为了截止时间
实时(real-time)系统面对的是另一类需求:任务带着截止时间(deadline),必须在规定时间内完成,否则后果严重甚至灾难。典型如工业控制、汽车刹车系统、航空航天、音视频实时编解码。
它的核心目标既不是吞吐也不是平均响应,而是确定性(determinism)——保证最坏情况下也能满足截止时间。实时系统又分两档:
- 硬实时(hard real-time):错过截止时间是不可接受的灾难(刹车系统晚了 10 毫秒可能出人命)。必须从数学上保证可调度性;
- 软实时(soft real-time):偶尔错过会降低质量但不致命(视频偶尔掉帧)。尽量满足,允许少量违约。
实时调度的机制特点:
- 强抢占 + 严格按优先级/截止时间:高优先级或更紧迫截止时间的任务必须能立即抢占当前任务。常用算法如速率单调(RM)、最早截止时间优先(EDF);
- 牺牲平均性能换确定性:实时系统常常故意不把 CPU 跑满(留余量应对突发),平均吞吐和利用率反而不如分时系统。
实时系统里的"实时"绝不等于"快",而是"准时"——它要的是可预测的、有保证的最坏延迟,哪怕平均性能差一些。一个平均很快但偶尔卡顿的系统,对硬实时场景是不合格的。
七、为什么调度不能脱离系统目标
把三类系统并排看,调度设计的底层逻辑就清楚了:没有一种调度策略能在所有指标上同时最优,因为指标本身互相冲突。
这张图是这一篇的核心:同一个"分配 CPU"的问题,因为三类系统的首要目标不同(吞吐 / 响应 / 确定性),调度的机制偏好(是否抢占、怎么排序、怎么切分时间)就分道扬镳了。
所以评判调度算法的好坏,必须先问"为了什么目标"。脱离系统目标谈"哪个算法最好"是没有意义的——SJF 平均周转时间最优,但用在实时系统里可能让某个长任务永远错过截止时间;RR 响应性好,用在批处理里却因频繁切换拖低吞吐。
调度从来不是纯算法问题,而是"目标驱动的资源分配设计"。先想清楚要优化什么,再去选算法——这个顺序一旦颠倒,再精巧的算法也是用错地方。
八、和工程实践 / SRE 的联系
这套"目标决定调度"的思维,在后端和 SRE 工作中处处适用:
- 在线服务像分时系统:Web/API 服务最看重的是响应时间(尤其是尾延迟 P99),而非吞吐总量。所以服务器、线程池、协程调度都偏向"快速响应每个请求、公平不饿死"——这就是分时哲学。
- 离线任务像批处理系统:Spark/Flink 批作业、夜间 ETL、大数据计算,看重的是整批吞吐和资源利用率,不在乎单个 task 多快出第一个结果——批处理哲学。把这类任务和在线服务混部时,必须隔离资源,否则批任务会拖垮在线服务的响应。
- 实时性需求的识别:金融交易撮合、实时风控、音视频流,带有软/硬实时特征,要为最坏延迟留余量、避免 GC 长停顿、慎用可能引入不确定延迟的机制。
- 别只看平均值:实时系统的教训对 SRE 极重要——平均延迟好看不代表系统健康,尾延迟(P99/P999)往往才是真正伤害用户的地方。监控和容量规划都要盯最坏情况,而不是平均值。
- K8s 调度与 QoS:Kubernetes 给 Pod 分 Guaranteed/Burstable/BestEffort 三档 QoS,本质就是在一个集群里同时服务"实时型"和"批处理型"负载——这是操作系统调度思想在编排层的重演。
学习这一部分最容易踩的坑
1. 一上来就背算法,跳过目标
FCFS、SJF、RR 是手段,吞吐/响应/确定性才是目的。脱离目标记算法,既不知道每个算法为什么这么设计,也无法在真实场景里选对。永远先问"这个系统要优化什么"。
2. 把"实时"理解成"更快"
实时的核心是准时和确定,不是平均速度快。一个平均很快但偶尔卡顿的系统,对硬实时是致命的;而一个平均慢但每次都能在 deadline 内完成的系统才合格。实时换取确定性,常常以牺牲平均性能和利用率为代价。
3. 混淆响应时间和周转时间
响应时间是"多久有第一个反应",周转时间是"多久整件事全部完成"。交互场景优化前者,批处理场景优化后者。两者经常冲突,分不清就会拿错指标去评判系统。
4. 以为公平就是绝对平均
分时系统追求公平,但公平不等于"所有任务严格平分 CPU"。合理的公平会结合优先级——重要任务多分一点也是公平的一种。绝对平均反而可能让关键任务得不到应有的资源。
总结
调度是操作系统在有限 CPU 上分配时间的核心机制,它的起点不是算法而是目标。核心要点:
- 调度的本质是反复回答两个问题——“选谁用 CPU”“给它用多久”,背后靠 PCB 和就绪队列支撑;
- 抢占式 vs 非抢占式是关键分水岭:决定任务能否霸占 CPU,现代通用系统普遍抢占;
- 核心指标(吞吐量、周转时间、响应时间、公平性、确定性)互相冲突,没有算法能全部最优;
- 批处理求吞吐(偏非抢占、短任务优先)、分时求响应与公平(抢占 + 时间片)、实时求确定性(强抢占 + 按截止时间);
- 评判调度必须先锁定系统目标,脱离目标谈"最优算法"没有意义;
- 工程上在线服务像分时、离线任务像批处理,SRE 尤其要盯尾延迟而非平均值。
下一篇我们带着这些指标,去逐一拆解 FCFS、SJF、RR 和优先级调度这四个经典算法,看它们各自在哪个目标上发力、又在哪个目标上失分。
参考资源:
- 《现代操作系统》(Andrew S. Tanenbaum)调度章节
- 《操作系统导论》(Operating Systems: Three Easy Pieces)调度引论
- OSTEP - Scheduling: Introduction
- 《操作系统概念》(Operating System Concepts,恐龙书)CPU 调度章节