一句话总结
调度的核心是在吞吐量、响应时间、公平性之间做权衡。五种经典算法:FCFS(先来先服务,简单但长任务堵死后面)、SJF(最短优先,平均等待最优但可能饿死短作业)、优先级(要配 Aging 防饥饿)、时间片轮转(交互式标配,时间片大小是关键)、多级反馈队列(兼顾两者,综合最优)。现代操作系统(Linux CFS)用红黑树按"虚拟运行时间"取最小者,追求完全公平。
初级理解
调度发生的时机
# 1. 进程从运行态 → 阻塞态(等 IO,主动让出 CPU)
# 2. 进程从运行态 → 就绪态(时间片用完,被抢占了)
# 3. 进程阻塞结束 → 就绪态(IO 完成,可能要抢回 CPU)
# 4. 进程终止
# 1 和 4 是"非抢占":不让也得让(没得选了)
# 2 和 3 是"抢占":可以决定换不换谁 —— 现代通用系统都是抢占式的
五个评价指标
| 指标 | 含义 | 谁关心 |
| CPU 利用率 | CPU 忙的时间比例 | 批处理系统 |
| 吞吐量 | 单位时间完成的进程数 | 批处理系统 |
| 周转时间 | 从提交到完成的总时间 | 批处理 |
| 等待时间 | 在就绪队列里等的时间 | 两者都关心 |
| 响应时间 | 从请求到首次响应 | 交互式系统 |
没有哪个算法全面最优:批处理追吞吐,交互式追响应,分时系统要公平。
中级深入
五种经典算法
# 1. FCFS 先来先服务
# 优点:简单公平;缺点: convoy effect(大车效应),一个长任务后面全等
# 场景:基本只用于历史概念或简单嵌入式
# 2. SJF 最短作业优先
# 平均等待时间数学上最优;但怎么知道要跑多久?(只能预测)
# 缺点:长作业可能被持续插队而饥饿
# 3. 优先级调度
# 静态优先级 / 动态优先级;配合 Aging(等待越久优先级越高)防饥饿
# 4. 时间片轮转 RR
# 每个进程跑一个时间片 q 就换下一个
# q 太大 → 退化成 FCFS;q 太小 → 切换开销占比过高
# 经验值:10-100ms,切换开销应控制在 1% 以内
# 5. 多级反馈队列 MLFQ
# 多个队列,优先级从高到低,时间片从短到长
# 新进程进最高优先级队列;用不完时间片就降级;高优先级空时才跑低优先级
# 既照顾短作业(响应快),又不饿死长作业(会逐级下沉但最终能跑完)
抢占式 vs 非抢占式
# 非抢占式:进程一旦拿到 CPU 就跑到阻塞或结束
# 实现简单,但一个死循环任务能让系统假死
# 抢占式:高优先级就绪或时间片用完就切换
# 现代通用 OS 标配(Windows/macOS/Linux/Android)
# 代价:上下文切换开销、并发控制的复杂性
高级进阶
Linux CFS 完全公平调度器
# 思路:不按固定优先级决定先后,而是按"虚拟运行时间"取最小者
# vruntime = 实际运行时间 × (nice 0 权重 / 该进程权重)
# 权重越高(nice 越小),vruntime 涨得越慢,被调度的机会越多
# 实现:红黑树按 vruntime 排序,每次取最左(最小)的进程运行
# 新进程/唤醒的进程 vruntime 会被设置成当前最小值附近 → 获得公平机会
# 调度类分层:SCHED_FIFO/SCHED_RR(实时)> SCHED_NORMAL(CFS)
# 实时进程永远优先于普通进程(除非让出)
多核与亲和性
# 每个 CPU 一个运行队列(减少锁竞争),负载不均时做均衡(load balancing)
# CPU 亲和性(taskset/affinity):把进程绑在指定核上
# 好处:缓存局部性更好,减少跨核迁移;坏处:可能造成负载不均
# NUMA:优先在进程所在节点分配内存,跨节点访问延迟高
实战场景
# 场景1:Java 线程优先级真的有用吗
thread.setPriority(Thread.MAX_PRIORITY); // 10
# Java 优先级映射到 OS 优先级,但只是"建议"
# 多数场景下 OS 调度器会自己找平,不要依赖优先级保证时序
# 场景2:为什么定时任务不要和核心服务抢 CPU
# 批量任务用 nice/ionice 降优先级,或 cgroup 限流
# docker run --cpus=2 --cpu-shares=512 限制容器 CPU 配额
# 经验:离线任务和在线服务混部时,必须做资源隔离,否则互相拖慢
# 场景3:观察调度行为
top → 看 PR(优先级)/ni(nice 值)/%Cpu(s) 的 sy 占比(切换开销)
vmstat 1 → r 列(运行队列长度)持续大于 CPU 核数 = 过载
pidstat -w 1 → 看 voluntary/involuntary 上下文切换次数
面试模拟
面试官:常见调度算法有哪些?各有什么问题?
你:FCFS 简单但有大车效应,一个长任务堵死后面;SJF 平均等待最优但长作业会饥饿,且运行时长难以预知;优先级调度要配 Aging 防饥饿;时间片轮转适合交互式,但时间片太大退化 FCFS、太小切换开销爆炸;多级反馈队列综合最好,新进程进高优先级队列、用完时间片就降级,兼顾响应与公平。
面试官:时间片设多大合适?
你:没有普适值,要平衡响应速度和切换开销。太小,上下文切换占比过高,有效计算比例下降;太大,交互式响应变差退化成 FCFS。经验是让切换开销控制在 1% 以内,通常 10 到 100 毫秒量级,再结合交互式与批处理混合场景做调整。
面试官:Linux 的 CFS 怎么做到公平?
你:CFS 给每个进程维护一个虚拟运行时间 vruntime,按权重加权:nice 值越高的进程 vruntime 涨得越慢。调度时从红黑树里取 vruntime 最小的进程运行,谁"被亏待"得多谁就先跑,从而逼近每个进程拿到等比例 CPU 的理想状态。实时调度类仍然优先于 CFS。