waitgroup 必须配合 goroutine 使用,单独调用 wait() 会永久阻塞;add() 必须在 go 语句前调用,done() 应用 defer 确保执行,否则计数器异常导致 hang 或 panic。

WaitGroup 必须和 goroutine 配合使用,单独调用 Wait() 会永久阻塞
这是最常见的卡死原因:误以为 WaitGroup 能“自动感知”异步任务结束。它不会——Wait() 只是等待内部计数器归零,而计数器增减完全靠你手动调用 Add() 和 Done()。如果没调 Add(1) 就 Wait(),或漏掉 Done(),程序就 hang 在那里。
正确姿势是:在启动 goroutine 前调 Add(1),在 goroutine 结束前调 Done()(通常用 defer):
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done() // 确保执行完才减计数
time.Sleep(100 * time.Millisecond)
fmt.Println("done")
}()
wg.Wait() // 这里才真正同步等待
多个 goroutine 场景下,Add() 必须在 go 语句之前调用
因为 Add() 操作不是原子的,如果写成 go func() { wg.Add(1); ... }(),就可能出现:goroutine 启动后还没来得及 Add(),主线程已执行到 Wait() 并发现计数为 0,直接返回——导致后续 Done() 调用 panic(“negative WaitGroup counter”)。
安全写法只有这一种顺序:
- 先
wg.Add(n)(n 是你要启的 goroutine 总数) - 再循环启动 n 个 goroutine,每个都带
defer wg.Done() - 最后
wg.Wait()
错误示例(会 panic):
for i := 0; i <h3>不能复用已 <code>Wait()</code> 过的 <code>WaitGroup</code> 实例</h3><p><code>sync.WaitGroup</code> 不是状态机,它内部计数器归零后,<code>Wait()</code> 返回,但之后不能再调 <code>Add()</code> ——Go 1.20+ 会 panic:“sync: negative WaitGroup counter”。旧版本可能静默失败或崩溃。</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill6460" title="Golang Naming"><img src="https://img.php.cn/upload/skill/000/000/081/179094616043400.jpg" alt="Golang Naming" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/skill6460" title="Golang Naming" class="overflowclass">Golang Naming</a> <p class="overflowclass">Go(Golang)命名规范 — 包括包、构造函数、结构体、接口、常量、枚举、错误、布尔值、接收器、getter/setter、函数等。</p> </div> <a rel="nofollow" href="/xiazai/skill6460" title="Golang Naming" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div><p>如果你需要多次等待不同批次任务,要么:</p>
- 每次新建
sync.WaitGroup{}实例 - 改用
sync.Once+ channel 或errgroup.Group(更推荐)
别试图重置计数器——没有公开 API 支持。
替代方案:errgroup.Group 更适合带错误传播的异步转同步
纯等完成用 WaitGroup 没问题,但一旦涉及错误收集、上下文取消、或想避免手动管理计数器,errgroup.Group 是更健壮的选择:
g, ctx := errgroup.WithContext(context.Background()) for i := 0; i <p>它自动处理计数、支持上下文、聚合第一个非-nil 错误——这些恰恰是手写 <code>WaitGroup</code> 容易漏掉的点。</p><p>WaitGroup 的计数器操作本身不加锁,但必须严格遵循“Add before go, Done in goroutine, Wait after all go”这个顺序;任何偏差都会立刻暴露为 panic 或死锁。实际项目里,越早换成 <code>errgroup</code> 或 <code>context</code> 驱动的模式,越少踩坑。</p>
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










