Hang Zhengyang
English ↗

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?
  • 当前拓扑和亲和性策略是否与数据访问模式匹配?