sync.waitgroup 是等待多个 goroutine 完成的最直接可靠方式,通过原子计数器管理任务数量,需在启动前调用 add()、结束前用 defer 调用 done()、主线程调用 wait() 阻塞等待,避免计数负值、漏计或 panic 导致未完成。

用 sync.WaitGroup 等待多个 goroutine 完成是最直接可靠的方式
Go 标准库的 sync.WaitGroup 就是为这个场景设计的:主线程不阻塞启动 goroutine,又能在所有子任务结束后再继续。它内部通过原子计数器管理 goroutine 数量,比手动 channel 通信或轮询更轻量、不易出错。
关键点在于:Add() 必须在 goroutine 启动前调用(否则可能漏计数),Done() 必须在每个 goroutine 结束前调用(通常用 defer 保底),Wait() 在主线程中阻塞直到计数归零。
常见错误现象包括:
-
panic: sync: negative WaitGroup counter——Done()调用次数多于Add() - 主线程提前退出,部分 goroutine 没执行完 ——
Add()漏调或位置不对 - goroutine 中 panic 导致
Done()未执行 —— 忘记用defer包裹
示例写法:
var wg sync.WaitGroup
for i := 0; i
<h3>为什么不能只靠 <code>time.Sleep</code> 或空 <code>select{}</code>
</h3>
<p>这两种方式看似“让主线程等一会儿”,但本质不可靠。</p>
<p><code>time.Sleep</code> 依赖预估耗时:太短会提前退出,太长拖慢整体流程;且无法感知 goroutine 是否真完成了工作(比如中间有网络重试、IO 阻塞)。</p>
<p>空 <code>select{}</code> 是永久阻塞,根本没提供“完成信号”,只能用于无限等待,完全不适用于需要同步完成的场景。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2525" title="Go语言(Golang)1.26.0"><img
src="https://img.php.cn/upload/manual/001/589/237/6a6adeed24a4a355.png" alt="Go语言(Golang)1.26.0" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2525" title="Go语言(Golang)1.26.0" class="overflowclass">Go语言(Golang)1.26.0</a>
<p class="overflowclass">Go语言(Golang)1.26.0版本官方下载,版本号 1.26.0,适合旧项目维护、兼容性测试和指定版本开发环境搭建。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2525" title="Go语言(Golang)1.26.0" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<p>使用场景上,只有调试临时观察输出时才可能用 <code>time.Sleep</code>,生产代码里必须避免。</p>
<h3>当 goroutine 需要返回结果或错误时,<code>sync.WaitGroup</code> 要配合其他机制</h3>
<p><code>sync.WaitGroup</code> 只管“是否结束”,不管“结果是什么”。如果每个 goroutine 要返回值或错误,得额外加结构来收集。</p>
<p>推荐组合方式是:<code>sync.WaitGroup</code> + <code>channel</code>(带缓冲)或 <code>slice</code> + <code>mutex</code>:</p>
- 用 channel 收集结果更符合 Go 的并发风格,尤其适合结果数量不确定或需流式处理
- 用 slice +
sync.Mutex更省内存、无 goroutine 泄漏风险,适合结果数量固定且已知 - 不要在 goroutine 里直接往未加锁的全局 slice 写 —— 数据竞争会导致 panic 或静默错误
性能影响很小:一个 WaitGroup 实例仅占 3 个 word(24 字节),原子操作开销可忽略;但若用无缓冲 channel 做结果收集,可能因接收方未及时读导致 goroutine 阻塞,反而卡住整个等待逻辑。
注意 WaitGroup 的复用限制和生命周期
sync.WaitGroup 不支持复用:一旦 Wait() 返回,内部计数器归零,再次 Add() 会 panic(Go 1.20+ 默认开启检查)。所以每次等待新一批 goroutine,都要用新的 WaitGroup 实例或重新初始化。
容易被忽略的点:
- 把
WaitGroup当参数传进 goroutine 时,务必传指针(*sync.WaitGroup),否则副本间计数不共享 - 不要在
Wait()返回后还对同一个实例调用Add(),除非你显式重置(但标准库没提供 Reset 方法,只能新建) - 在 HTTP handler 或长期运行服务中反复使用时,建议局部声明,避免意外跨请求复用
复杂点往往不在语法,而在计数时机和作用域——写完记得检查每处 Add 和 Done 是否配对、是否在正确作用域内执行。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










