WaitGroup.Add() 必须在 goroutine 启动前调用,且 Add 与 Done 必须成对、在对应 goroutine 中执行;传参必须用指针,禁止值传递或复用未完成的 WaitGroup。

WaitGroup.Add() 必须在 go 语句之前调用
这是最常触发 panic: sync: negative WaitGroup counter 的原因。WaitGroup 不会“监听” goroutine 启动,它只认计数器值;如果 wg.Wait() 执行时计数器还是 0,它立刻返回——你看到的“程序秒退”“日志没打全”,基本都源于此。
常见错误写法包括:
-
go func() { wg.Add(1); defer wg.Done(); ... }()——Add在 goroutine 内,主 goroutine 已经Wait()完了 - 循环中漏掉某次
wg.Add(1)(比如if分支跳过)——导致Wait()永远阻塞 - 先
wg.Wait(),再wg.Add(1)—— 计数器未初始化就等待,行为未定义
正确做法只有一种:确定任务数后,一次性 wg.Add(n),再启动所有 goroutine。哪怕 n=1,也不能省。
动态任务数场景下 Add(n) 的数值必须精确匹配实际 goroutine 数量
协程池、从 channel 拉取任务、或按文件列表启 worker,都属于“启动前不确定总数”的情况。但 WaitGroup 并不支持运行时扩容,Add 必须在 Wait() 前完成,且数值必须等于真实启动的 goroutine 次数。
立即学习“go语言免费学习笔记(深入)”;
容易出错的点:
- 用
for range ch启 goroutine,却在循环外wg.Add(len(tasks))—— channel 长度不可靠,可能为空或阻塞 - 循环变量捕获导致少启或多启:如
for i := range tasks { go func() { process(i) }() },所有 goroutine 共享最后一个i值,最终只处理一个任务,但你可能误以为启了 N 个 - 把
wg.Add(1)放进 select 的某个 case 里,而其他分支没加 —— 任务漏注册
安全做法是:每次从 channel 拿到一个任务,就立刻 wg.Add(1),再 go 处理。不要攒着统一加。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
Done() 必须与 Add() 成对,且只能在对应 goroutine 中调用
wg.Done() 等价于 wg.Add(-1),但它不是“归还资源”,而是声明“我这个 goroutine 的任务完成了”。跨 goroutine 调用、统一回收、或在中间件里批量 Done(),都会让计数器变负并 panic。
典型陷阱:
- 函数有多个
return点,只在末尾写wg.Done()—— error 分支提前退出,计数器卡住 - 用了
defer wg.Done(),但 goroutine 内部 panic 且没 recover —— 实际上defer在 panic 时仍会执行,这点没问题;真正危险的是你忘了defer,或写了defer又手动调了一次wg.Done() - 在 pool 的
Close()方法里遍历所有 worker 并wg.Done()—— 这不是 WaitGroup 的使用方式,它不维护 goroutine 列表
唯一稳妥写法:defer wg.Done() 放在 goroutine 函数入口处,且确保该 goroutine 是唯一负责自己 Done() 的主体。
WaitGroup 必须传指针,禁止值传递或复制
sync.WaitGroup 是值类型,但内部含原子计数器和 mutex;按值传递(如 func worker(wg sync.WaitGroup))会拷贝一份独立状态,子 goroutine 调 Done() 修改的是副本,主 goroutine 的 Wait() 永远等不到归零。
表现很隐蔽:无 panic、无报错、程序安静退出,但结果缺失——因为你等的根本不是那组 goroutine。
必须做到:
- 声明为
wg := &sync.WaitGroup{}或var wg sync.WaitGroup后传&wg - 函数参数接收
wg *sync.WaitGroup - 闭包内引用的
wg变量本身已是地址(即声明时已取址)
另外注意:WaitGroup 不能复用,除非上一轮 Wait() 已完成且无 goroutine 在运行中;不要试图 Reset() 或重置计数器——Go 标准库没有提供该方法,强行清零会导致竞态。

















