3. 常见调度算法(上):先来先服务、短作业优先、时间片轮转

调度算法这东西,说白了就是「谁先上机器」的排队规则。我早年刚接触分布式计算时,总觉得调度不就是排个队嘛,有啥好研究的?直到有一次线上集群因为调度策略不当,导致大量短任务被长任务堵死,整个系统吞吐量直接腰斩……嗯,从那以后我再也不敢小看调度算法了。

今天咱们先聊三个最经典的调度算法:先来先服务(FCFS)、短作业优先(SJF)、时间片轮转(RR)。别看它们简单,很多现代调度器(比如 Kubernetes 的默认调度器、Linux 的 CFS)的核心思想,都能追溯到这些基础算法上。

3.1 先来先服务(FCFS)

FCFS 是最朴素的调度策略。谁先到,谁先上。就像银行柜台排队,你来得早,就优先办理。

核心逻辑:

  • 维护一个 FIFO(先进先出)队列
  • 新任务到达时,追加到队列尾部
  • 调度器从队列头部取出任务执行
  • 任务执行完毕,再取下一个

优点:

  • 实现简单,几乎零开销
  • 公平性直观——先来后到,没人插队
  • 不会发生饥饿(每个任务最终都会被执行)

缺点:

  • 平均等待时间可能很长
  • 对短任务不友好——一个长任务堵在前面,后面所有短任务都得等着
  • 不适合交互式系统

我踩过的坑: 曾经在一个批处理集群里用了 FCFS,结果有个跑了两天的数据清洗任务把后面几百个秒级任务全堵死了。运维同学半夜打电话问我「为啥提交的任务一直不跑」……从那以后,我但凡看到纯 FCFS 的调度策略,都会多问一句「你们确定没有短任务吗?」

适用场景: 任务执行时间大致相同、且对响应时间不敏感的场景。比如离线日志处理、定时报表生成。

3.2 短作业优先(SJF)

SJF 的思路很直接:谁先做完,谁先上。调度器预估每个任务的执行时间,然后挑最短的那个先执行。

你想想看,这就像超市收银台——如果有个顾客只买一瓶水,你让他先结账,后面买一车东西的人多等一会儿,整体效率反而更高。

核心逻辑:

  • 调度器需要知道(或预估)每个任务的执行时间
  • 每次调度时,从就绪队列中选择执行时间最短的任务
  • 可以是非抢占式的(任务一旦开始,就执行完)
  • 也可以是抢占式的(称为 SRTF,最短剩余时间优先)

优点:

  • 理论上能获得最小平均等待时间
  • 对短任务极其友好
  • 系统吞吐量高

缺点:

  • 长任务可能永远得不到执行——这就是饥饿问题
  • 需要预估执行时间,实际中很难做到精确
  • 如果预估不准,效果可能比 FCFS 还差

注意: 纯 SJF 在实际生产环境中几乎不可用。原因很简单——你没法准确知道一个任务要跑多久。我见过有人用历史数据做预估,结果遇到数据倾斜时,预估时间差了十倍,调度直接乱套。

适用场景: 任务执行时间可预估、且长任务可以接受一定延迟的场景。比如 MapReduce 中的小任务调度。

3.3 时间片轮转(RR)

RR 算法解决了 FCFS 和 SJF 的一个核心痛点:响应时间不可控。它的做法是给每个任务分配一个固定长度的时间片(time slice),轮流执行。

说白了,就是「每人玩一会儿,时间到了换人」。这就像幼儿园老师让小朋友轮流玩滑梯——每人玩 2 分钟,然后换下一个。

核心逻辑:

  • 维护一个就绪队列(通常是循环队列)
  • 每个任务执行一个时间片(比如 100ms)
  • 时间片用完,任务还没结束,就把它放回队列尾部
  • 调度器继续取队列头部的下一个任务

关键参数:时间片大小

时间片大小 优点 缺点
过大 上下文切换开销小 响应时间变长,退化为 FCFS
过小 响应时间短,交互性好 上下文切换开销大,CPU 利用率下降

优点:

  • 响应时间可控,适合交互式系统
  • 公平性好——每个任务都能得到 CPU 时间
  • 不会发生饥饿

缺点:

  • 上下文切换开销不可忽视
  • 平均等待时间可能比 SJF 长
  • 时间片大小需要精心调优

我的经验: 时间片大小一般设置在 10ms~100ms 之间。Linux 早期版本默认时间片是 100ms,后来 CFS 调度器改用了更精细的虚拟运行时间概念。我个人习惯是:如果是 IO 密集型任务,时间片可以小一点(10-20ms);如果是 CPU 密集型,可以大一点(50-100ms)。

适用场景: 交互式系统、分时操作系统、需要保证响应时间的场景。

3.4 三种算法对比

特性 FCFS SJF RR
调度依据 到达时间 执行时间 时间片
平均等待时间 高 低(理论最优) 中等
响应时间 差 差(长任务可能饥饿) 好
饥饿问题 无 有(长任务) 无
实现复杂度 低 中(需要预估时间) 中
适用场景 批处理 可预估的批处理 交互式系统

3.5 核心逻辑流程图

下面我用一张 SVG 图来展示这三种算法的核心调度逻辑对比。你可以直观地看到任务在队列中是如何被选择和执行的。

三种调度算法核心逻辑对比 FCFS 先来先服务 就绪队列(FIFO) T1 T2 T3 ... CPU 执行 从队列头部取任务 执行完毕再取下个 不可抢占 SJF 短作业优先 就绪队列(按时间排序) T2 2s T1 5s T3 8s ... CPU 执行 选执行时间最短的 可抢占(SRTF) 长任务可能饥饿 RR 时间片轮转 循环队列 T1 T2 T3 ... CPU 执行 时间片 100ms 时间片用完→放回队尾 轮流执行 上下文切换有开销

这张图把三种算法的核心差异展示得很清楚:FCFS 是排队上车,SJF 是让跑得快的先跑,RR 是每人玩一会儿。你想想看,这三种思路其实对应了三种不同的「公平」定义——先来后到、效率优先、机会均等。

3.6 小结

这三种算法是调度领域的「三原色」。虽然现代调度器(比如 Linux CFS、Kubernetes 调度器)已经复杂得多,但核心思想都能追溯到它们身上。

我个人建议初学者先把这三种算法吃透。你可以在纸上画一画任务到达时间、执行时间,然后手动模拟调度过程。我当年就是这么干的——画了整整一沓纸,才真正理解为什么 SJF 的平均等待时间最短,为什么 RR 的响应时间最好。

嗯,下一节咱们会聊更高级的调度算法,包括优先级调度、多级反馈队列这些。但今天这三个,是基础中的基础,值得你花时间好好消化。

一句话总结: FCFS 保公平,SJF 提效率,RR 保响应。没有银弹,只有适合场景的选择。

可可精品资料库