WaitGroup死锁必然发生而非偶发:值传递导致Done无效、defer wg.Done()未置首行、Add/Done数量不匹配均会使wg.Wait()永久阻塞。

WaitGroup 死锁不是“偶尔发生”,而是只要 Add() 和 Done() 不严格配对,或传递方式错误,就必然卡住 —— 它不会报错,只会让 wg.Wait() 永远挂起。
值传递导致 Done() 完全无效
这是最隐蔽的坑:把 sync.WaitGroup 当普通结构体传进 goroutine,比如 go worker(url, wg)。Go 会复制整个结构体,而 sync.WaitGroup 内部含互斥锁和计数器,副本之间完全隔离。
-
go vet会直接报:passes sync.WaitGroup by value - 必须改用指针:
go worker(url, &wg),且函数签名得是func worker(url string, wg *sync.WaitGroup) - IDE(如 GoLand、VS Code + gopls)通常会在参数位置标黄并提示 “contains sync.Locker”
defer wg.Done() 放错位置,提前 return 就漏调
写在函数中间或末尾,一旦前面 http.Get 失败、os.Open 权限不足、json.Unmarshal 出错,defer 根本不执行。
- 正确做法:把
defer wg.Done()放在函数第一行,确保任何退出路径都触发 - 配合
if err != nil { return err }早期返回,比“逻辑上总会走到最后”可靠得多 - 尤其在文件下载、HTTP 请求、数据库查询等易错场景中,漏掉一次
Done()就等于整个wg.Wait()永不返回
for 循环中 Add() 和 Done() 数量不匹配
常见于闭包捕获循环变量、漏调 Add(1)、或在循环外一次性 Add(len(urls)) 却没启动对应数量的 goroutine。
立即学习“go语言免费学习笔记(深入)”;
- 典型错误:
for _, url := range urls { go fetch(url) },但没在循环里调wg.Add(1) - 闭包陷阱:
for i := 0; i → 所有 goroutine 共享同一个 <code>i,且可能漏调Done()多次或零次 - 安全写法:
for _, url := range urls { wg.Add(1); go func(u string) { defer wg.Done(); ... }(url) }
和 channel 混用时,无缓冲通道阻塞导致 Done() 永不执行
当 goroutine 在 out 处卡住(因为 <code>out 是无缓冲通道,且主 goroutine 还没开始 range out),defer wg.Done() 就永远不运行。
- 修复核心:接收逻辑不能放在
wg.Wait()之后,必须提前启动一个独立 goroutine 消费out - 错误顺序:
启动 worker → wg.Wait() → range out - 正确顺序:
启动 receiver goroutine → 启动 worker → wg.Wait() - 缓冲通道不是解药:设
make(chan int, N)只是延迟死锁,不能根治;真正要解决的是“发送端是否总能被消费”
最麻烦的不是某一行写错,而是多个问题叠加:比如值传递 + defer 放错 + 无缓冲 channel —— 这种组合会让调试变成猜谜。盯住 wg 的生命周期:谁 Add()、谁 Done()、谁在等、谁在发,缺一不可。


















