wg.add(n)必须在启动所有worker前调用,值等于worker数;每个worker开头defer wg.done();jobs channel需带缓冲且容量匹配峰值。

worker 启动前必须完成 wg.Add(),否则可能漏等
常见错误是把 wg.Add(1) 放在 go worker() 内部,或和 go 启动语句顺序颠倒。结果是部分 worker 还没来得及注册就退出,wg.Wait() 提前返回,后续往 results channel 发送数据时 panic 或丢结果。
-
wg.Add(n)必须在启动所有 worker goroutine 之前一次性调用,且值等于 worker 数量 - 每个 worker 函数开头立刻
defer wg.Done(),确保无论正常 return 还是 panic 都能通知 - 别依赖
for job := range jobs自动退出就省略Done()—— 它不会等你写完日志、释放资源再走
jobs channel 必须带缓冲,且大小要匹配业务峰值
无缓冲 make(chan Job, 0) 在 HTTP handler 等短生命周期场景里极其危险:一旦所有 worker 忙碌,jobs 会直接阻塞当前协程,拖垮整个请求链路。而缓冲太小(比如只设 10)又无法应对突发流量,导致上游写入失败或重试风暴。
- 缓冲大小建议设为预期最大待积压任务数,例如下游处理延迟平均 100ms、QPS 峰值 500 → 缓冲至少
500 * 0.1 * 2 ≈ 100 - 缓冲区过大(如 10000)会掩盖真实积压问题,让监控失真;建议配合 metrics 暴露
len(jobs)实时水位 - 不要用
len(jobs) == cap(jobs)判断是否满——这是竞态读,应改用select+default非阻塞尝试
关闭 jobs channel 的时机必须严格在任务提交完成后
提前 close(jobs) 会导致还没发完的任务被 worker 丢弃;延后 close 则 worker 一直阻塞在 range,wg.Wait() 永不返回。典型错误是在 for 循环发任务中途调用 close,或在 goroutine 里异步 close 但没同步信号。
- 关闭动作只能由生产者(主 goroutine)执行一次,且必须在所有
jobs 之后 - 如果任务来自多个 goroutine(比如多个 HTTP 请求并发提交),需用
sync.WaitGroup或chan struct{}协调“全部提交完毕”信号 - 别在 worker 里 close(
jobs) —— 运行时 panic: “close of closed channel”
results channel 不要盲目 close,消费端需适配未关闭场景
很多实现习惯在 wg.Wait() 后 close(results),但若消费端用 for res := range results,它会等 channel 关闭才退出;而如果上游有超时控制或主动中断,results 可能永远不关,消费协程卡死。
- 推荐 results 使用带缓冲 channel(
make(chan Result, N)),消费端用for i := 0; i 显式取完 - 若必须用 range,应在 close(
results) 前确保所有 worker 已退出(即wg.Wait()返回后),且消费端有超时兜底 - results 中的
Result结构体若含指针或 map/slice 字段,务必深拷贝或确认只读,否则多个 worker 并发写同一内存触发 data race
平滑退出最难的部分不是“怎么关”,而是“怎么确认所有任务都已真正落地”——比如 HTTP 请求是否发出去、DB 事务是否 commit、日志是否 flush。这些操作不能藏在 worker 函数末尾,得显式等待或封装进 Job 自身的 Done callback 里。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











