调度算法:FCFS / SJF / RR / MLFQ

一句话:CPU 一次只能跑一个执行实体,调度器决定”下一个该谁上”。在早期 OS 里调度单位是进程,现代 OS 里调度单位是线程(Linux 里两者底层统一为 task)。

FCFS=先来先得,SJF=短活先干,RR=每人玩一会儿轮流,MLFQ=优先级高的先上但防止饿死。


一、从零开始:为什么需要调度?

先搞清楚调度是啥

 
你的电脑同时开着:
 
  浏览器(在下载文件)
 
  VS Code(在等你打字)
 
  微信(在收消息)
 
你以为它们在"同时运行"?
 
其实 CPU 在飞快地来回切换——这秒给浏览器,下秒给 VS Code。
 

调度 = 调度器在多个就绪的执行实体(现代 OS 里是线程)之间,决定下一个让谁上。

就像奶茶店只有一个店员,但有 10 个顾客排队。店员叫”下一位!“的时候,选谁?这就是调度。

调度发生的三种情况

 
① 线程自己主动让出 CPU(比如等 IO):"我歇会儿,你先来"
 
② 线程的时间片用完了(被强制赶下来):"你到时间了,下去"
 
③ 有优先级更高的线程来了(被抢走):"对不起,领导优先"
 

二、调度目标——可能先问这个

目标意思人话
公平每个执行实体都有机会别让一个线程饿死
高效CPU 利用率高别让 CPU 闲着
低延迟用户感觉不到卡点击立刻响应
高吞吐单位时间干完的活多一天写完 10 个任务
不饿死所有线程都能被执行到别让短任务饿死长任务

不同的目标 → 不同的调度算法。


三、四种算法逐个讲

1. FCFS(First Come First Served)——先来先得

2. SJF(Shortest Job First)——短作业优先

3. RR(Round Robin)——时间片轮转

4. MLFQ(Multi-Level Feedback Queue)——多级反馈队列

一句话

谁先到就谁先上,直到执行完才换下一个。

生活类比

排队买奶茶:

 
张三 7:00 到 → 排在第一个
 
李四 7:01 到 → 排在第二个
 
王五 7:02 到 → 排在第三个
 
张三点了一杯做起来很复杂的(执行时间长)
 
李四和王五只能在后面干等
 

例子

 
进程  到达时间  需要执行时间
 
P1    0         10
 
P2    1         1
 
P3    2         1
 
FCFS 调度顺序:
 
P1 → P2 → P3
 
等待时间:
 
P2 等了 9 个单位(P1 跑了 10,P2 第 1 到,第 10 才开始)
 
P3 等了 10 个单位
 
平均等待时间 = (0 + 9 + 10) / 3 = 6.33
 

优点 vs 缺点

优点缺点
实现最简单护航效应:长进程挡着,短进程排长队
公平(按到达顺序)不适合分时系统(大家都要用电脑)
 
护航效应(Convoy Effect):
 
一个耗时长的进程在前面慢慢跑,
 
后面一堆短进程(本来 1 秒就能完事)干等。
 
类比:高速上一个慢车在最左道开 60码,
 
后面一堆车堵着,明明几秒钟就能过去。
 

一句话

哪个进程需要的执行时间最短,就先执行哪个。

生活类比

超市收银台:

 
FCFS 模式:A 买了一车东西在结账,B 只买了一瓶水,排在后面等
 
                 → B 骂街
 
SJF 模式:B 只买一瓶水 → 先让 B 结账
 
          A 买一车东西 → 后面慢慢结
 
                 → 整体排队时间缩短
 

例子

 
进程  到达时间  需要执行时间
 
P1    0         10
 
P2    1         1
 
P3    2         1
 
SJF 调度顺序:
 
P1(只有 P1 到了,先跑 P1)
 
→ 第 1 时刻 P2 到了(P2 比当前 P1 短?不,P1 已经在跑了)
 
→ 第 2 时刻 P3 到了
 
→ 第 10 时刻 P1 跑完,选最短的 P2(1)→ P3(1)
 
P1 跑了 10 → P2 跑 1 → P3 跑 1
 

等一下,非抢占式 SJF 是这样。但还有抢占式 SJF(也叫 SRTF,Shortest Remaining Time First)

 
抢占式 SJF:
 
P1 跑到第 1 时刻 → P2 到达,P2 只需要 1,P1 还剩 9
 
→ 把 P1 赶下来,先跑 P2
 
P2 跑完 → P3 到了,P3 只需要 1,P1 还剩 9
 
→ 先跑 P3
 
P3 跑完 → 继续跑 P1
 
平均等待时间更短!
 

核心问题

 
SJF 有一个致命缺陷:
 
它需要预先知道每个进程要跑多久
 
——操作系统怎么可能知道?!
 
优点缺点
平均等待时间最短(理论上最优)无法预知执行时间
吞吐量高长进程可能饿死(总有更短的插队)
 
实际系统中不用纯粹的 SJF,
 
但这个思想很重要——"短任务优先"是很多现代调度器的核心
 

一句话

每个进程轮流跑一小段时间(时间片),跑完没结束就排到队尾,换下一个。

生活类比

朋友聚餐轮流唱歌:

 
KTV 包间,5 个人,每人只能唱 1 分钟:
 
你唱 1 分钟 → 下一个人唱 1 分钟 → 再下一个人...
 
一轮结束,又轮到你唱 1 分钟
 
没唱完的歌 → 下一轮接着唱(不会被中断)
 
→ 每个人都唱过了,没人饿着
 

工作方式

 
时间片 = 4 个单位
 
进程队列:[P1] [P2] [P3]
 
时间 0 - 4:  跑 P1,没跑完,排到队尾
 
时间 4 - 8:  跑 P2,跑完了!移除
 
时间 8 - 12: 跑 P3,没跑完,排到队尾
 
时间 12 - 16:继续跑 P1,跑完了!移除
 
时间 16 - 20:继续跑 P3,跑完了!
 

时间片大小——最关键的设计


graph LR

    A[时间片太大] --> B[退化成 FCFS]

    A --> C[响应慢]

    D[时间片太小] --> E[上下文切换太多]

    D --> F[CPU 都在切换,没干活]

时间片效果类比
太大(100ms)用户体验差,感觉卡每人唱半小时才换人
太小(1ms)切换开销大,CPU 都在做切换没干活刚开口就被打断
合适(10-50ms)交互流畅,开销可接受每人唱 1 分钟,正好
 
上下文切换开销:
 
  每次从进程A切到进程B 要花 0.1ms
 
  时间片设 100ms → 切换占 0.1%
 
  时间片设 1ms   → 切换占 10%(浪费!)
 

优点 vs 缺点

优点缺点
公平——每个进程都有机会时间片设置困难
响应快——适合交互系统吞吐量可能不如 SJF
没人会饿死切换开销
 
适用场景:分时操作系统(Linux、Windows)
 
你和室友共用一台服务器:
 
  你在写代码,他在下载电影,她在看网页
 
  RR 让你们都觉得"电脑是我的!"
 

一句话

系统有多个不同优先级的队列,进程在队列之间动态升降级——优先跑短任务,但长任务也不会饿死。

这是目前操作系统真正在用的调度算法!

先讲”多级队列”是啥

 
系统有好几个队列,每个队列优先级不同:
 
队列 0(优先级最高)→ 时间片 10ms
 
队列 1(优先级中等)→ 时间片 50ms
 
队列 2(优先级最低)→ 时间片 200ms
 
只有高优先级队列空了,才处理下一个队列
 

”反馈”是啥意思

 
反馈 = 进程的优先级会动态变化
 
       不傻等,根据行为调整
 

MLFQ 三大规则

 
规则 1:优先跑高优先级队列里的进程
 
         队列 0 有进程 → 不碰队列 1
 
规则 2:同队列内用 RR(轮流)
 
         队列 0 有两个进程 → 轮流跑 10ms
 
规则 3:时间片用完了没跑完 → 降一级
 
         进程在队列 0 跑了 10ms 没结束
 
         → 移到队列 1
 
         在队列 1 跑了 50ms 没结束
 
         → 移到队列 2
 

逐步动画

 
进程 P1 进来了:
 
① 放到队列 0(新进程默认最高优先级)
 
   跑 10ms(时间片用完了)→ 还没结束
 
   ↓(降级)
 
② 放到队列 1
 
   跑 50ms → 还没结束
 
   ↓(降级)
 
③ 放到队列 2(最低优先级)
 
   跑 200ms → 可能就结束了
 
如果 P2 这时候新进来:
 
  P2 → 队列 0(优先级更高!)
 
  先跑 P2,P1 在队列 2 等着
 
 
这个设计巧妙在哪?
 
  短进程:在队列 0 一个时间片就结束了 → 响应极快
 
  长进程:慢慢降级到低优先级 → 不会饿死,但不会抢短进程的
 
  IO 密集型:频繁主动让出 CPU → 留在高优先级
 
  CPU 密集型:总用满时间片 → 自然降到低优先级
 

解决”饥饿”的补充规则

 
规则 4:每隔一段时间,把所有进程移到最高优先级队列
 
  → 防止低优先级进程永远吃不到 CPU
 
  → "全体都有,重置!"
 

生活类比

医院急诊分诊:

 
队列 0(红色/危重):马上救!  → 类比短进程(响应极快)
 
队列 1(黄色/紧急):等一会儿 → IO 密集型
 
队列 2(绿色/普通):慢慢等   → CPU 密集型长任务
 
一个病人(进程)刚来:
 
  → 放队列 0,跑最快时间片
 
  如果治了很久没好(时间片用完):
 
  → 降到队列 1,给更长时间
 
  还是没好:
 
  → 降到队列 2,保证不会被遗弃
 
每过一段时间:"全体重新检查"(重置)
 
→ 队列 2 的人也可能发现其实很严重,回到队列 0
 

优点 vs 缺点

优点缺点
短任务响应快(交互流畅)参数配置复杂(几个队列?时间片多大?)
IO 密集型进程自然优先生效可能出现调度抖动
长任务不会饿死
不需要预先知道执行时间

额外的奖励规则(防止被恶意利用)

 
问题:如果一个进程在时间片用完前 1ms 主动让出 CPU,
 
      它算"没跑完时间片" → 不会降级!
 
恶意进程:
 
  跑 9ms → 主动让出 → 留在高优先级
 
  又跑 9ms → 主动让出 → 一直赖在最高级
 
解决方案:
 
  记录进程"实际占用的 CPU 总时间"
 
  真·时间到了还是会降级
 

四、一张表横向对比

算法核心思想预先知道执行时间?会不会饿死?响应速度复杂性
FCFS先来先得不需要不会饿死最简单
SJF最短优先需要(实际不可能)长进程可能饿死简单
RR轮流,时间片不需要不会饿死中等
MLFQ多级队列动态升降不需要不会饿死(有重置规则)最快最复杂

五、一句话讲清

Q:调度算法有哪些?说几个常用的

经典的四种:FCFS、SJF、RR、MLFQ。FCFS 最简单但短进程体验差;SJF 理论最优但没法预知执行时间;RR 轮转保证公平和响应速度,时间片设置是关键;MLFQ 是目前操作系统真正用的,综合了 SJF 和 RR 的优点。

Q:FCFS 的护航效应是什么?

长进程先到并执行,后面短进程排长队等待。就像高速上一个慢车在最左道开 60,后面堵一串。平均等待时间很长。

Q:RR 的时间片怎么设?

不能太大(退化成 FCFS 响应慢),不能太小(切换开销太大)。一般 10-50ms 比较合适,切换开销控制在 1% 以内。

Q:MLFQ 为什么能同时保证低延迟和高吞吐?

短进程在最高优先级一个时间片就结束了,响应极快。长进程会降级到低优先级,不会抢短进程的 CPU。再加上定期重置防止饿死,短任务快、长任务也有保障。

Q:MLFQ 怎么防止恶意进程?

记录进程实际占用的 CPU 时间,不只看”用没用完时间片”。


六、记忆口诀

 
FCFS 先来先得——排队买奶茶,慢的堵死快的
 
SJF 短的优先——理论最牛,实际上用不了
 
RR 轮流坐庄——时间片到了换人,交互系统必备
 
MLFQ 多级升降——Linux 在用的真·王者
 

总结:FCFS 排队、SJF 短活优先(但用不了)、RR 分时间片轮转、MLFQ 分优先级动态升降。MLFQ 是实际系统的答案,记住三条核心规则 + 一个重置机制。

速记卡(面试闪卡)

Q1:一句话讲清「调度算法:FCFS / SJF / RR / MLFQ」到底是什么?

A:调度器决定多个就绪线程谁先上 CPU,经典有先来先得、短优先、轮转、多级反馈四种。

Q2:题目核心 —— 怎么理解?

A:像奶茶店一个店员十人排队,叫”下一位”选谁就是调度;英文 CPU Scheduling,目标是公平高效、低延迟不饿死。

Q3:思路拆解 —— 怎么理解?

A:如同 FCFS 排队买奶茶慢的堵死快的(护航效应);SJF 短活先干理论最优却没法预知时长;英文 Convoy Effect 描述长进程挡道。

Q4:代码骨架 —— 怎么理解?

A:好比 RR 聚餐轮流唱 K:每人唱一小段(时间片)没完排尾续;时间片太大退化 FCFS、太小全在切换;英文 Round Robin 时间片轮转。

Q5:复杂度与实战 —— 怎么理解?

A:如同医院急诊分诊:MLFQ 多队列动态升降级,短任务快、长任务不饿死,是 Linux 真在用的;英文 Multi-Level Feedback Queue。

Q6:核心速记主线有哪些?

  • FCFS:先来先得,简单但有护航效应拖慢短任务

  • SJF:短作业优先,平均等待最短但需预知时长、可能饿死

  • RR:时间片轮转,公平响应快,时间片大小是关键

  • MLFQ:多级反馈队列,动态升降级,Linux 实际采用

口诀

A:调度四法记心间

先来先得慢的堵

短活优先用不了

轮转多级 Linux 用

相关链接