sync.waitgroup 是需配合 add/wait/done 使用的计数器类型,add 必须在 goroutine 启动前调用,done 必须严格匹配 add 总和,不可复制、不可重用,不支持超时。

sync.WaitGroup 不是“等待函数”,也没有叫 Wait 的独立函数——它是 sync.WaitGroup 类型的 方法,必须配合计数器管理使用。直接调用 Wait() 而不配 Add() 和 Done(),程序要么立刻返回(计数为 0),要么永远卡住(计数 > 0 且没人减)。
WaitGroup.Add 必须在 goroutine 启动前调用
这是最常引发 panic: sync: negative WaitGroup counter 或 Wait() 提前返回的原因。
-
Add(1)必须出现在go语句之前,确保主协程串行设置计数 - 错误写法:
go func() { wg.Add(1); defer wg.Done(); ... }()→Add可能被 Wait 先看到,也可能被漏掉,竞态发生 - 正确写法:
wg.Add(1); go func() { defer wg.Done(); ... }() - 循环启动时,每轮都要先
Add(1)再go,不能把Add塞进闭包里
Done 调用必须严格匹配 Add 总和
Done() 等价于 Add(-1),它不做正数校验,只原子减一。一旦减出负数,立刻 panic。
- 分支提前 return 时容易漏掉
defer wg.Done(),比如 HTTP 请求失败后直接 return,没走到 defer 行 - 重复调用:
defer wg.Done()+ 手动wg.Done()→ 计数变负 - 闭包捕获循环变量导致多个 goroutine 共享同一个
done调用机会 → 某些Done()被跳过 - 推荐写法:所有 goroutine 入口第一行就写
defer wg.Done(),别挪到中间或条件块里
WaitGroup 不可复制、不可重用
结构体值传递会浅拷贝内部字段(包括计数器和 waiter 队列),造成“主 wg 等不到子 goroutine”的静默失效。
- 函数参数必须是
*sync.WaitGroup,不能是sync.WaitGroup - 不能写
w2 := wg或append(wgs, wg),这等于造了个新计数器 -
Wait()返回后,wg 内部状态已进入终态;再次Add()是未定义行为,官方不保证安全 —— 别试图“重置”,直接声明新变量:wg := sync.WaitGroup{} - HTTP handler 等长生命周期对象里反复用同一个 wg,大概率在第二轮 panic
Wait 不支持超时,别硬套 select
Wait() 本身没有上下文或超时机制。强行用 select { case 容易掩盖真实问题。
- 超时后
Wait()仍在后台阻塞,goroutine 可能还在跑,资源未释放 - 正确做法:用
context.WithTimeout控制业务逻辑生命周期,在 goroutine 内监听ctx.Done()主动退出,再调wg.Wait() - WaitGroup 只回答一个问题:“所有 Done 是否已执行?” 它不管任务是否该停、是否出错、是否超时 —— 这些得由你自己的控制流决定
真正难的不是记住三步,而是当代码混入 error 处理、channel 收发、嵌套 goroutine、甚至 defer 链时,还能一眼看清每个 Add 是否有对应 Done,以及它们是否在正确的 goroutine 作用域里执行。计数失配不会报错,它只会在上线后某个凌晨悄悄让服务 hang 住。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











