sync.waitgroup用于主goroutine等待所有子goroutine完成,需严格遵循add、done、wait三步;常见错误包括goroutine内add、重复wait、漏done、值传递;应传指针并可结合context实现超时控制。

在Go语言中,sync.WaitGroup 是协调多个 goroutine 执行完毕最常用、最轻量的方式。它的核心作用是:**让主 goroutine 阻塞等待一组子 goroutine 全部退出后,再继续执行后续逻辑**。
WaitGroup 的基本用法三步走
使用 WaitGroup 必须严格遵循三个操作,缺一不可:
- Add(n):在启动 goroutine 前调用,告诉 WaitGroup “即将启动 n 个任务”。注意:必须在 goroutine 启动前调用,且不能在 goroutine 内部调用 Add(除非加锁);
- Done():每个 goroutine 执行完后调用一次,表示该任务已完成。通常放在函数末尾或 defer 中,确保即使 panic 也能执行;
- Wait():主 goroutine 调用,会阻塞直到内部计数器归零。
常见错误与规避方式
以下写法看似合理,实则容易引发 panic 或死锁:
- 在 goroutine 内部调用 Add(1):可能导致 WaitGroup 计数器未初始化就 Wait,或 Add 在 Done 之后执行,造成计数不匹配;
- 多次调用 Wait():Wait 不是可重入的,重复调用可能 panic(Go 1.20+ 会 panic);
- 忘记调用 Done():Wait 永远阻塞,程序卡住;
- WaitGroup 作为值传递:结构体按值传递会复制,子 goroutine 调用的是副本的 Done(),主 goroutine 的 Wait() 永远等不到 —— 务必传指针。
一个安全可靠的示例
下面是一个典型场景:并发请求多个 URL,并等待全部响应完成:
func fetchAll(urls []string) {
var wg sync.WaitGroup
wg.Add(len(urls)) // 一次性声明所有任务数
for _, url := range urls {
go func(u string) {
defer wg.Done() // 确保完成时计数减一
resp, err := http.Get(u)
if err != nil {
log.Printf("failed to fetch %s: %v", u, err)
return
}
defer resp.Body.Close()
log.Printf("fetched %s, status: %s", u, resp.Status)
}(url) // 注意:传参避免闭包引用循环变量
}
wg.Wait() // 主协程在此等待全部完成
log.Println("all requests done")
}
进阶提示:配合 Context 实现带超时的等待
WaitGroup 本身不支持超时。若需“最多等 5 秒”,应结合 context.WithTimeout 使用:
- 启动 goroutine 时传入 context;
- 在 goroutine 内监听
ctx.Done()提前退出; - 主 goroutine 使用
select等待wg.Wait()或ctx.Done(); - 注意:超时后仍需确保 wg.Done() 已被调用(比如用 defer),否则 WaitGroup 计数不一致。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











