什么是 IO 密集,什么是 CPU 密集?
按瓶颈资源给任务分类:CPU 密集(CPU-bound)是算力打满、CPU 一直在算; IO 密集(IO-bound)是大部分时间在等磁盘、网络、数据库返回,CPU 反而闲。 判断标准很硬:CPU 时间占墙钟时间的比例。这个分类直接决定线程池大小、要不要用协程、以及优化往哪使劲。
1. 两类任务的特征
| 维度 | CPU 密集 | IO 密集 |
|---|---|---|
| 时间花在 | 计算、循环、加解密、压缩 | 等网络/磁盘/DB 响应 |
| CPU 利用率 | 接近 100% | 低,常低于 20% |
| 线程状态 | 一直在 R | 大量在 S(可中断睡眠) |
| 加线程有用吗 | 没用,超过核数只会增加切换 | 有用,等待可以重叠 |
| 优化手段 | 并行化、算法、SIMD、缓存 | 批量、异步、连接池、减少往返 |
| 典型场景 | 视频转码、哈希、排序、风控计算 | Web API、爬虫、网关、DB 访问 |
CPU 密集: [算 100ms][算 100ms][算 100ms] 墙钟 300ms,CPU 300ms
IO 密集: [等 90ms][算 10ms][等 90ms][算 10ms] 墙钟 200ms,CPU 20ms
不同任务可以交叠:
T1 [等db 90ms][算10ms]
T2 [等db 90ms][算10ms] ← 等待期间 CPU 可服务别的任务
2. 怎么判定
CPU 占比 = (utime + stime) / real_time × 100%
> 80% → CPU 密集
< 20% → IO 密集
中间地带 → 混合型,按主要耗时归类
# 方法一:time 直接给出三者
time ./job
# real 0m2.010s user 0m0.180s sys 0m0.020s → CPU 只占 10%,IO 密集
# 方法二:运行中观察
pidstat -u -p 1234 1 # %CPU 长期很低但 QPS 不低 → IO 密集
pidstat -w -p 1234 1 # cswch/s 高(自愿切换)多半在等 IO
# 方法三:火焰图看栈里是不是卡在 syscall
perf record -F 99 -p 1234 -g -- sleep 30 && perf report --stdio | head -30
3. 线程池该开多大
IO 密集的经验公式(Little 定律的工程化版本):
最佳并发数 ≈ 核数 × (1 + 平均等待时间 / 平均计算时间)
例如 8 核、一次请求等 DB 90ms、计算 10ms:8 × (1 + 9) = 80 个工作线程比较合适。
再往上加只会增加上下文切换和内存占用。
// Go 里不必手算线程数:协程阻塞在 IO 时自动让出,M 的数量由 GOMAXPROCS 控制
var wg sync.WaitGroup
sem := make(chan struct{}, 200) // 用信号量控制对下游的并发压力
for _, url := range urls {
wg.Add(1)
sem <- struct{}{}
go func(u string) {
defer wg.Done()
defer func() { <-sem }()
resp, err := http.Get(u) // 阻塞在这里,但底层 M 可以去跑别的 G
if err == nil { resp.Body.Close() }
}(url)
}
wg.Wait()
并发数不是越大越好:下游数据库连接数、文件句柄、带宽都会先到瓶颈, 压测时要观察的是下游的排队时间,而不是本机的线程数。
4. 常见追问
Q:IO 密集为什么适合协程而不是线程? 线程的栈按 MB 计,切换要进内核;协程栈初始只有 KB 级,切换在用户态完成,几万个并发也只占几百 MB。 而且协程"遇到阻塞自动让出"的语义,让同步写法获得异步的并发度。
Q:混合型任务怎么处理? 拆开:CPU 部分丢进固定大小的计算线程池(等于核数),IO 部分用大量轻量协程/异步调用。 不要用同一个池子,否则慢 IO 会把计算线程占满,形成饥饿。
Q:CPU 密集任务真的不能加线程吗? 可以少量加(核数 + 1~2),用来掩盖偶发的缺页、GC 停顿。但超过核数后,吞吐不会提升, 反而因为缓存失效和调度开销而下降。
Q:为什么说"IO 密集的服务加机器没用"? 如果瓶颈在下游数据库,加应用机器只会把更多并发压到数据库上,延迟更糟。 此时应该加缓存、批量化、读写分离,先降低单位请求的 IO 次数。
小结
分类看的是"CPU 时间 / 墙钟时间"这个比值。CPU 密集靠并行化和算法,线程数贴近核数;
IO 密集靠重叠等待,并发数按"核数 × (1 + 等待/计算)"估,并受下游容量约束。
答这题时给公式、给 time 的输出示例,再点一句"瓶颈可能在下游",深度就够了。