直接用 go f() 不适合高并发任务,因其无节制启动 goroutine 会迅速耗尽内存或压垮下游服务;worker pool 通过固定数量常驻 worker 复用实现可控并发,避免资源失控与调度开销。

为什么直接用 go f() 不适合高并发任务
因为无节制启动 goroutine 会迅速耗尽内存或压垮下游服务。比如每秒收 1000 个 HTTP 请求,每个都 go handle(req),可能瞬间创建上万 goroutine,调度开销剧增,还容易触发 runtime: goroutine stack exceeds 1GB limit 这类错误。
Worker Pool 的核心不是“并发”,而是“可控并发”——用固定数量的长期 goroutine 持续消费任务队列,避免启停开销和资源失控。
- 典型场景:日志异步写入、批量消息投递、图片缩略图生成
- 关键区别:
go f()是“一次一启”,Worker Pool 是“复用 N 个常驻 worker” - 性能影响:worker 数设为 CPU 核心数 × 1.5~3 倍较稳妥;设太高反而因上下文切换拖慢整体吞吐
chan Job 作为任务队列时的阻塞与死锁风险
如果用无缓冲 channel(make(chan Job))当任务队列,而所有 worker 都在忙、channel 又没消费者就绪,生产者会永久阻塞在 jobs 。更糟的是,若 worker 因 panic 退出且没 recover,channel 就再没人读了,整个池子卡死。
必须加缓冲 + 超时控制:
立即学习“go语言免费学习笔记(深入)”;
- 任务 channel 至少用带缓冲的:
jobs := make(chan Job, 100),缓冲大小按峰值 QPS × 预估平均处理时长估算 - 发任务时加超时:
select { case jobs - 每个 worker 必须用
defer func() { if r := recover(); r != nil { ... } }()防止单个 panic 停掉整个 worker
如何安全关闭 Worker Pool 并等待任务完成
粗暴地 close(jobs) 不行——它只让后续 send 失败,但已入队但未被消费的任务会丢失;用 sync.WaitGroup 也不够,因为 worker 可能正卡在 上等新任务,不主动退出。
正确做法是“双 channel 协作”:
- 加一个
done := make(chan struct{})通知停止接收新任务 - worker 循环改成:
for { select { case job, ok := - 关闭前先 close(
jobs),再 close(done),最后wg.Wait() - 注意:不能只 close(
jobs) 就 wg.Wait(),否则 worker 仍阻塞在,永远等不到“关闭信号”
要不要用第三方库?ants 和原生实现的取舍点
ants 确实封装了自动伸缩、panic 捕获、统计等功能,但多数业务场景不需要动态扩缩容——固定 4~8 个 worker 足够应对 90% 的后台任务。自己写 50 行以内的 pool 更透明、更易调试。
真正该用 ants 的情况只有两个:
- 任务耗时差异极大(有的 10ms,有的 5s),且 QPS 波动剧烈
- 需要实时监控 worker 使用率、排队数、拒绝数等指标
- 其他时候,手写 pool 的
jobs、results、done三 channel 结构足够清晰,出问题一眼能看出是哪个 channel 卡住了
最常被忽略的一点:无论用不用 ants,worker 内部调用的外部服务(比如 HTTP client、DB query)本身是否有超时设置?这才是实际压测中 80% 的“假死”根源,不是 pool 本身的问题。










