CPU 调度与负载¶
学习目标¶
学完本章后,学习者应该能够:
- 理解 CPU 调度、时间片、优先级和负载的含义。
- 区分 CPU 密集型任务和 IO 密集型任务。
- 理解上下文切换、锁竞争和容器 CPU limit 对 Go 服务的影响。
- 能初步定位 Go 服务 CPU 飙高问题。
CPU 调度是什么¶
机器上的可运行任务通常比 CPU 核心多。操作系统调度器负责决定哪个线程在什么时候使用 CPU。
调度器关注:
- 公平性:不能让某个任务长期饿死。
- 响应性:交互或服务请求要及时响应。
- 吞吐量:单位时间完成更多工作。
- 优先级:重要任务可以获得更多机会。
后端工程师不需要实现调度器,但要理解 CPU 不是无限资源。
时间片与优先级¶
时间片是任务连续使用 CPU 的一小段时间。时间片用完后,调度器可能切换到其他任务。
优先级决定任务获得 CPU 的倾向。Linux 中可以通过 nice 值、cgroups、容器 CPU limit 等方式影响调度。
在 Kubernetes 中,容器的 CPU request 和 limit 会影响调度与运行:
- request:调度时声明需要多少 CPU。
- limit:运行时最多能用多少 CPU。
CPU limit 设置过低时,服务可能被节流,表现为延迟周期性升高。
CPU 密集型与 IO 密集型¶
CPU 密集型任务主要消耗计算资源:
- 加解密。
- 压缩。
- JSON 大量编解码。
- 图片或音视频处理。
- 大量正则匹配。
IO 密集型任务主要等待外部资源:
- 数据库查询。
- Redis 请求。
- HTTP 调用。
- 文件读写。
- 网络传输。
优化方向不同。CPU 密集型要减少计算、并行化或优化算法;IO 密集型要设置超时、连接池、批处理、缓存和异步化。
Go 后端实际应用例子¶
例子一:用 pprof 定位 CPU 热点¶
Go 服务可以开启 pprof:
采集 CPU profile:
重点看哪些函数占用 CPU 时间最多,再判断是算法问题、序列化问题、日志问题,还是 GC 压力。
例子二:控制 CPU 密集型任务并发¶
CPU 密集型任务不适合无限并发。并发数超过 CPU 能力后,可能只是增加上下文切换。
func workerPool(tasks <-chan Task, workers int) {
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for task := range tasks {
task.Run()
}
}()
}
wg.Wait()
}
workers 通常要结合 CPU 核数、任务耗时、容器 CPU limit 和压测结果调整。
常见误区¶
- 误区一:CPU 高一定是坏事。
CPU 高可能说明机器资源被充分利用,也可能说明计算异常。要结合吞吐、延迟和错误率判断。
- 误区二:加 goroutine 就能提高 CPU 密集型任务性能。
并发超过 CPU 能力后,更多 goroutine 可能只带来调度开销和锁竞争。
- 误区三:容器里看到的 CPU 核数一定等于可用 CPU。
容器可能被 cgroups 限制。Go 服务需要关注 GOMAXPROCS、CPU request、limit 和实际节流指标。
线上问题案例¶
某服务上线新日志字段后 CPU 飙高。排查 pprof 发现大量 CPU 消耗在 JSON 序列化和字符串拼接上。日志量随请求量线性增长,且每条日志都包含大对象。
修复方式包括减少热路径日志、避免打印大对象、使用结构化日志字段、抽样记录,以及把详细日志放到错误分支。
实战任务¶
给一个 Go HTTP 服务接入 pprof,并完成一次 CPU profile 分析:
- 增加 pprof 监听端口。
- 构造一段会消耗 CPU 的接口。
- 采集 30 秒 CPU profile。
- 找出最耗 CPU 的函数。
- 写出一个优化方向。
参考答案
可以在服务启动时单独开启 127.0.0.1:6060 的 pprof 端口,然后用 go tool pprof 采集 /debug/pprof/profile?seconds=30。如果热点函数是 JSON 编解码,可以考虑减少字段、避免重复序列化、缓存结果或换更合适的数据格式。
pprof 分析时不要只看函数名,要结合请求路径、流量变化和最近代码变更。CPU profile 说明“CPU 时间花在哪里”,不直接说明“为什么业务变慢”,还需要和延迟、QPS、GC、外部依赖指标一起看。
面试题¶
1. CPU 密集型和 IO 密集型任务有什么区别?¶
参考答案
CPU 密集型任务主要时间花在计算上,例如加密、压缩、编解码、复杂算法。IO 密集型任务主要时间花在等待外部资源上,例如数据库、网络、文件、缓存。
CPU 密集型优化重点是减少计算、优化算法、控制并发和利用多核;IO 密集型优化重点是超时、连接池、缓存、批处理、异步化和依赖治理。
2. 为什么上下文切换过多会导致性能下降?¶
参考答案
上下文切换需要保存和恢复执行现场,本身有开销。同时切换会破坏 CPU 缓存局部性,让后续执行更容易访问慢速内存。
当线程过多、锁竞争严重或任务频繁阻塞唤醒时,CPU 可能花太多时间在调度上,业务代码实际执行时间反而变少。
3. Go 服务 CPU 飙高怎么排查?¶
参考答案
先确认 CPU 高是否伴随延迟升高、错误率上升或吞吐下降。然后采集 pprof CPU profile,查看热点函数;同时观察 GC 指标、goroutine 数量、最近发布、日志量、流量变化和容器 CPU 节流。
常见原因包括死循环、低效算法、大量序列化、正则匹配、日志格式化、GC 压力和锁竞争。