sync.waitgroup 仅负责计数与等待,关键在并发启动与安全收尾:add 必须在 goroutine 启动前调用,不可在 goroutine 内或并发调用,避免漏加、多加、竞态及闭包变量捕获错误。

sync.WaitGroup 不是为“语言学习任务”设计的,它只负责计数与阻塞等待——你传给它的每个 goroutine,不管是在翻译句子、调用 LLM API 还是解析语法规则,它都一视同仁:加一、减一、归零即放行。关键不在“学什么”,而在“怎么并发启动 + 怎么安全收尾”。
WaitGroup.Add 必须在 goroutine 启动前调用
常见错误是把 wg.Add(1) 放进 go func() { ... }() 里面,或者漏掉、多调、在循环外一次性 Add(len(tasks)) 却中途跳过某些 task。结果不是 wg.Wait() 永远卡住(计数器始终为 0),就是 panic 报 sync: WaitGroup misuse: Add called concurrently with Wait。
正确姿势:
- 循环中每个任务启动前立刻
wg.Add(1),且确保无竞态 - 若任务列表已知长度,也可在循环外统一
wg.Add(len(tasks)),但必须保证后续每个 task 都确实启动了 goroutine - 绝不在 goroutine 内部调用
Add
闭包捕获循环变量导致所有 goroutine 打印同一个 ID
写批量处理时容易这么写:
for i := 0; i <p>结果所有 goroutine 都处理 <code>tasks[len(tasks)-1]</code>,甚至 panic 索引越界。根本原因是匿名函数捕获的是变量 <code>i</code> 的地址,而非当前值。</p> <p>修复方式只有一种:显式传参</p> <pre class="brush:php;toolbar:false;">for i := 0; i <h3>Done 调用不匹配会 panic 或死锁</h3> <p><code>wg.Done()</code> 必须和 <code>wg.Add(1)</code> 严格一一对应。漏调 → <code>wg.Wait()</code> 永不返回;多调 → panic <code>sync: negative WaitGroup counter</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>典型风险点:</p>
- 任务出错提前 return,但没走
defer wg.Done() - 用了多个
return分支,只在一处写了defer - 在循环里误写成
for ... { wg.Done() },重复调用
最稳妥写法永远是:defer wg.Done() 放在 goroutine 函数第一行,且该函数只做一件事(封装 task 处理逻辑)。
WaitGroup 无法感知任务是否真正退出
wg.Wait() 返回只表示计数器归零,不代表所有 goroutine 已调度完毕或内存已释放。比如你在 goroutine 里启了一个子 goroutine 做异步日志上报,主 goroutine 在 wg.Wait() 后就退出了,那个日志 goroutine 可能还在跑,甚至 panic。
若需强一致性退出,必须配合:
-
context.Context传递取消信号 - 额外的
done chan struct{}等待子 goroutine 显式关闭 - 避免在 goroutine 内再启未受控的 goroutine
批量语言学习任务常涉及 HTTP 请求、文件读写、模型推理——这些操作本身可能超时或阻塞,sync.WaitGroup 对它们完全无感。它只是个计数器,不是执行控制器。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










