图 1:本文封面,SJF 调度下的执行顺序与平均周转时间结论。
先说明属性:这是一篇笔试真题复盘,题目出自本地历史笔试资料整理,与任何公司的当前招聘无关。原题以选择题形式出现:现有 4 个同时到达的作业 J1、J2、J3、J4,执行时间分别是 1、3、5、7 小时,系统按单道方式运行并采用短作业优先(SJF)算法,问平均周转时间是多少。选项里最迷惑人的是 8 和 6 这类整数——它们分别对应「顺序执行直接平均」和「算错口径」的典型错法。这类题真正考的不是算术,是周转时间的定义和 SJF 的执行顺序推演。
先立定义:周转时间 = 完成时刻 − 到达时刻。
这是整道题的钥匙。四个作业同时到达,到达时刻都是 0,所以每个作业的周转时间就等于它的完成时刻。很多错选都错在把周转时间当成执行时间,或者当成等待时间——执行时间 1、3、5、7 的平均是 4,等待时间又是另一笔账,口径一混全盘皆错。单道方式运行意味着同一时刻只有一个作业在执行,没有并发,执行顺序就是一条排队的直线。
第一步:确定 SJF 的执行顺序。
短作业优先,顾名思义是谁的执行时间短谁先跑。四个作业同时到达、全部在队列里,没有新到达插队的问题,所以执行顺序就是执行时间的升序:J1(1 小时)→ J2(3 小时)→ J3(5 小时)→ J4(7 小时)。
第二步:逐个算完成时刻。
- J1 从 0 时刻开始,执行 1 小时,完成于时刻 1,周转时间 1;
- J2 从时刻 1 开始,执行 3 小时,完成于时刻 4,周转时间 4;
- J3 从时刻 4 开始,执行 5 小时,完成于时刻 9,周转时间 9;
- J4 从时刻 9 开始,执行 7 小时,完成于时刻 16,周转时间 16。
第三步:取平均。(1 + 4 + 9 + 16) ÷ 4 = 30 ÷ 4 = 7.5 小时。
写一段程序把这个推演跑一遍,顺便验证一个更强的结论(Node.js 22.15.0,node sjf.js):
SJF(按执行时间升序):
J1(耗时1h,完成于1h) → J2(耗时3h,完成于4h) → J3(耗时5h,完成于9h) → J4(耗时7h,完成于16h)
平均周转时间 = (1+4+9+16)/4 = 7.5 小时
FCFS(假设按 J4 J3 J2 J1 顺序到达):
J4(耗时7h,完成于7h) → J3(耗时5h,完成于12h) → J2(耗时3h,完成于15h) → J1(耗时1h,完成于16h)
平均周转时间 = (7+12+15+16)/4 = 12.5 小时
枚举全部 24 种顺序:最优平均周转 7.5 小时,最差 12.5 小时
注意对照组:如果改用先来先服务(FCFS),且长作业恰好排在前面,平均周转时间直接涨到 12.5 小时,比 SJF 多出整整 5 小时。更关键的是最后一行——把四个作业的全部 24 种执行顺序枚举一遍,7.5 小时就是全局最小值。
为什么 SJF 一定最优:相邻交换论证。
这不是枚举出来的巧合,有可证明的道理。假设某个排列里一个长作业 L 排在一个短作业 S 前面,交换它们俩的位置:排在它们前面的作业不受影响,排在它们后面的作业整体提前了(L − S)的时间;而 L 和 S 自己的完成时刻之和也变小了——交换前两者完成的时刻之和比交换后多出 L − S。总周转时间严格变小。也就是说,任何「长在前、短在后」的相邻逆序都可以通过交换改进,所以最优排列里不可能存在逆序,唯一没有逆序的排列就是执行时间升序。这个论证模板可以套用到一大批「排队顺序优化」类题目上。
图 2:SJF 与 FCFS 甘特图对照、最优性的交换论证与现实代价。
答题之外的追问:SJF 的现实代价。
笔试题到这里就结束了,但面试官往往会追问一句「既然 SJF 最优,操作系统为什么不全用它」。两个硬伤:一是它需要预知作业的执行时间,现实里调度器拿不到这个信息,只能用历史行为估算;二是长作业可能长期饥饿——只要短作业源源不断到达,长作业就永远轮不上,需要老化(aging)之类的机制补偿。下次遇到「周转时间」三个字,先默念一遍公式再动笔:完成时刻减到达时刻。你在笔试里被这类调度题坑过吗?错在了定义还是顺序?欢迎聊聊。