进程调度算法有哪些?

2026年 阅读约 undefined 分钟 undefined

CPU 只有一个,怎么决定下一个跑谁?本文讲清 FCFS、SJF、优先级、时间片轮转、多级反馈队列五种经典调度算法,批处理与交互式系统怎么选,Linux CFS 完全公平调度思路,附 Java 线程优先级与面试高频问答。

一句话总结

调度的核心是在吞吐量、响应时间、公平性之间做权衡。五种经典算法: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。