Worker Pool 是为限流和复用而设,避免无节制启 goroutine 导致内存耗尽或压垮服务;需按 IO/CPU 密集型合理设 worker 数,用带缓冲或无缓冲 channel 控制背压,确保每个任务结果必回传且不丢数据,关闭时须严格遵循“停投递→等消费→收结果”三步。

为什么直接用 go f() 不够,而要上 Worker Pool
因为无节制启 goroutine 会吃光内存或压垮下游服务。比如你有 10 万条 URL 要抓取,每条起一个 goroutine,瞬间 10 万并发,DNS、连接池、文件描述符全爆。Worker Pool 的本质是「限流 + 复用」:固定数量的 worker 持续从队列取任务,既控住并发数,又避免反复创建销毁开销。
常见错误现象:runtime: out of memory、dial tcp: lookup failed、HTTP 服务返回 429 Too Many Requests —— 这些往往不是逻辑错,是没做并发节制。
- 典型使用场景:批量 HTTP 请求、日志写入、图片转码、数据库批量插入
- worker 数量 ≠ CPU 核心数;IO 密集型(如 HTTP)通常设为 10–100,CPU 密集型才接近
runtime.NumCPU() - 不加缓冲的 channel(
make(chan Job, 0))会让 sender 阻塞,适合背压敏感场景;加缓冲(make(chan Job, 1000))能吞吐突发,但可能掩盖积压问题
chan Job 和 chan Result 怎么设计才不丢数据
核心原则:worker 必须把每个任务的执行结果(无论成功失败)都发回 resultChan,否则主 goroutine 在 range resultChan 时永远等不到关闭信号,导致程序卡死。
容易踩的坑:select 里没写 default 或没处理 resultChan 已满的情况,导致 worker panic 后静默退出,任务结果丢失。
立即学习 “go 语言免费学习笔记(深入)”;
- Job 结构体至少带唯一 ID 或原始输入字段,方便结果溯源:
type Job struct {ID string; URL string} - Result 必须包含 job 关联字段(如
JobID)和 error:type Result struct {JobID string; Data []byte; Err error } - worker 内部不要直接
close(resultChan);由主 goroutine 在所有 job 发送完毕后 closejobChan,再等所有 worker 退出后 closeresultChan - 如果结果 channel 有缓冲,务必检查是否写满 —— 可用
select+default做非阻塞发送,失败时记录告警
如何安全关闭 Worker Pool 并等待全部完成
关闭不是调个函数的事,而是三步协同:停投递 → 等消费 → 收结果。漏掉任一环都会导致 panic、死锁或数据丢失。
典型错误:close(jobChan) 后立刻 close(resultChan),但 worker 还在往 resultChan 写,触发 send on closed channel panic。
- 正确顺序:先
close(jobChan),再用sync.WaitGroup等所有 worker 退出(worker 内部用for job := range jobChan自动退出),最后close(resultChan) - worker 函数开头 defer wg.Done(),确保无论 panic 还是正常退出都通知 WaitGroup
- 主 goroutine 用
for i := 0; i 而不是 <code>range resultChan,避免因 channel 提前关闭导致少读结果 - 超时控制必须加:WaitGroup 等待和 result 接收都要设 timeout,防止某个 worker 卡死拖垮整个流程
要不要用第三方库?ants 和原生实现差在哪
如果你只做简单 IO 限流,ants 确实省事:pool.Submit(func()) 一行提交,自动扩容缩容。但它隐藏了 channel 控制权,调试时看不到任务积压位置,也不支持自定义结果路由。
原生实现看似多写几十行,换来的是完全可控:你能精确知道当前排队多少任务、哪个 worker 卡住了、结果怎么分发到不同 handler —— 这些在微服务间调度、重试策略、熔断降级时全是刚需。
-
ants默认最大 worker 数硬编码为 10w,且扩容是 runtime 检测延迟后触发,无法响应预期内的流量峰 - 原生方案中,
jobChan缓冲区大小 = 可接受的最大积压量,这个数字业务方最清楚(比如“最多容忍 5 秒内积压 2000 条”) - 真正难的不是启动 pool,而是当某 worker 执行超时或 panic 后,怎么把未完成 job 重新入队、标记重试次数、隔离异常 worker —— 这些得自己补逻辑,
ants不管
复杂点不在语法,在状态管理:job 生命周期(pending/running/done/failed)、worker 健康度、结果归集一致性。这些没标准解法,得按你的错误容忍度和监控能力来拍板。