sync.waitgroup是go语言中协调goroutine执行的核心同步原语,通过add增计数、done减计数、wait阻塞等待计数归零,三步缺一不可;不可用time.sleep或空循环替代,因其不精确、不安全且无法感知goroutine状态。

Go 语言中没有叫 Wait 的内置函数能直接“阻塞主控函数”——你真正需要的是协调 goroutine 生命周期的同步原语,最常用、最可靠的是 sync.WaitGroup。
为什么不能直接用 time.Sleep 或空 for 循环来“等待”
常见误区是用 time.Sleep(10 * time.Second) 或 for {} 硬等,这既不精确也不安全:
- 硬休眠时间设短了,goroutine 没跑完主函数就退出;设长了浪费资源,且无法响应完成信号
- 死循环会 100% 占满一个 CPU 核,还可能被编译器优化掉(尤其空循环)
- 完全无法感知 goroutine 是成功、panic 还是卡死
sync.WaitGroup 的正确用法:三步缺一不可
WaitGroup 不是“自动发现 goroutine”,它靠显式计数。漏掉任一步都会导致 panic 或永远阻塞:
-
wg.Add(1)必须在go语句前调用(不是 goroutine 内部),否则存在竞态 - goroutine 执行完必须调用
wg.Done()(或defer wg.Done()) - 主 goroutine 调用
wg.Wait()会阻塞,直到内部计数归零
示例:
func main() {
var wg sync.WaitGroup
wg.Add(1) // ← 关键:先加
go func() {
defer wg.Done() // ← 关键:确保执行
fmt.Println("goroutine running...")
time.Sleep(2 * time.Second)
fmt.Println("done")
}()
wg.Wait() // ← 主 goroutine 在此阻塞,直到 wg.Done() 被调用
fmt.Println("main exit")
}
替代方案:什么时候该用 chan 而不是 WaitGroup
当你要等的不只是“结束”,还要“结果”或“错误”时,WaitGroup 就不够用了:
-
WaitGroup只回答“是否结束”,不传递值;chan可以接收返回值、error 或状态码 - 多个 goroutine 需要按顺序处理结果?用带缓冲的
chan+range更自然 - 需要超时控制?
select+time.After是标准做法,WaitGroup本身不支持超时
例如带超时的等待:
done := make(chan bool, 1)
go func() {
time.Sleep(3 * time.Second)
done <h3>容易被忽略的坑:全局变量、多次 <code>Wait</code>、未初始化</h3><p>这些错误不会立刻报错,但会导致难以调试的行为:</p>
- 把
WaitGroup声明为包级变量并复用?计数可能残留,下次Wait会立即返回或 panic - 对同一个
WaitGroup多次调用Wait()?第二次起直接 panic:sync: WaitGroup is reused before previous Wait has returned - 声明了
var wg sync.WaitGroup但没调用Add就Wait?会立刻返回(因为计数是 0),看似“等到了”,实则逻辑错误
最稳妥的做法:每个等待场景都用局部 var wg sync.WaitGroup,严格配对 Add/Done,不跨函数复用。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











