Week 3:Process / Context Switch / Scheduling(高密度版)
关键视角
调度不是“把 CPU 分给谁”这么简单,而是在公平性、吞吐、尾延迟之间做实时折中。
对低延迟系统,最重要的问题是:切换行为是否破坏了 cache/TLB 局部性与时间确定性。
Context Switch 到底切了什么
逻辑上至少包含:
- 通用寄存器、PC、SP 等执行上下文
- 内核调度实体状态
- 地址空间切换相关状态(进程级)
真正的性能损失常来自“间接代价”:
- cache 热数据被挤出(尤其 L1/L2)
- TLB 有效项下降(或地址空间切换引入额外翻译成本)
- branch predictor 局部性变差
所以 context switch 成本不是常数,而是与工作集形态和切换频率耦合。
进程 vs 线程:别停留在“共享内存/不共享”
工程上更关键的比较:
- 隔离粒度:进程隔离强,线程隔离弱。
- 切换路径:线程切换通常较轻,但共享状态导致竞争更重。
- 故障域:线程 bug 更容易扩散到全进程。
- NUMA/亲和性:线程迁移会打破局部性,进程 pinning 可更明确控制拓扑。
低延迟场景常见策略:少量固定线程 + CPU pinning + 明确 ownership,而不是“线程池越大越好”。
调度器与你代码的交互面
你控制不了调度器全部行为,但可影响“被调度的形状”:
- 任务粒度(过细任务导致调度开销占比升高)
- 阻塞点数量(锁/IO 导致 sleep-wakeup 链)
- 优先级与亲和性策略
- run queue 压力(burst 提交 vs 平滑提交)
典型反模式
- 一个热点锁后挂大量线程,导致 convoy 效应。
- 为了“吞吐”无限放大并发,最后 p99 恶化。
- 忽视核间迁移,导致 cache 热度持续归零。
调度目标冲突(必须接受 trade-off)
- 公平性高:短期内每个任务都能运行,但热路径可能被打断。
- 吞吐高:批处理效率好,但请求级延迟可能拉长。
- 尾延迟优先:需要牺牲部分资源利用率和公平性。
系统设计时,先选主目标,再设参数;不要指望默认调度策略自动满足业务 SLA。
观测与诊断框架
你要盯的不是单指标,而是关联:
- context switch rate 上升时,p99 是否同步上升?
- run queue length 与服务时间抖动是否相关?
- CPU migration 增多时,cache miss 是否恶化?
常见分析组合:
- 调度事件 tracing(切换、唤醒、迁移)
- CPU affinity/拓扑检查
- 热路径 flame graph + 时间线关联
实战策略(偏低延迟)
- 把关键线程固定在隔离 CPU(减少迁移与干扰)。
- 控制线程数在“可并行 + 可保持局部性”区间。
- 优先消灭会阻塞关键路径的锁与 syscall。
- 对短任务采用批次/合并提交,降低调度频率。
你要能回答的 4 个问题
- 为什么这段代码在低负载快、高负载抖?
- 抖动来自计算本身,还是调度/切换放大?
- 增加线程为什么没提高吞吐,反而拖慢 p99?
- 当前拓扑和亲和性策略是否与数据访问模式匹配?