如何使用Golang的waitgroup等待多个goroutine完成_Golang并发同步与协调方法

1次阅读

waitgroup 的 add 必须在 goroutine 启动前调用,且不可复制、需传指针;它仅同步完成状态,结果收集需配合 channel 或加锁共享变量。

如何使用Golang的waitgroup等待多个goroutine完成_Golang并发同步与协调方法

WaitGroup 必须在 goroutine 启动前 Add

很多人在循环启动 goroutine 时,把 wg.Add(1) 放在 goroutine 内部,导致 Wait() 永远阻塞或 panic。这是因为 AddDone 必须配对,且 Add 要在 Go 语句之前调用,否则可能还没来得及 Add 就执行完了 Done。

正确做法是:

var wg sync.WaitGroup for _, job := range jobs {     wg.Add(1) // ← 必须在这里,不是在 goroutine 里     go func(j string) {         defer wg.Done()         process(j)     }(job) } wg.Wait()
  • 如果漏掉 wg.Add(1)Wait() 会立即返回(计数为 0),无法等待任何任务
  • 如果 Add 在 goroutine 内、且没加锁或同步,可能出现竞态:多个 goroutine 同时修改计数器
  • 传参时务必用值拷贝(如 (job)),避免闭包引用循环变量

WaitGroup 不能被复制,必须传指针或全局/字段级使用

sync.WaitGroup 是含 mutex 的结构体,复制后会导致内部状态不一致,运行时报 fatal Error: sync: WaitGroup is reused before previous Wait has returned

常见错误写法:

立即学习go语言免费学习笔记(深入)”;

func badExample(wg sync.WaitGroup) { // ← 复制了 wg!     wg.Add(1)     go func() { wg.Done() }()     wg.Wait() // panic! }
  • 函数参数、返回值、赋值语句中都禁止直接传递 WaitGroup 值类型
  • 应传 *sync.WaitGroup,或作为结构体字段、包级变量使用
  • 方法接收者也必须是 *WaitGroup,标准库所有方法都是指针接收

WaitGroup 不提供结果收集能力,需配合 channel 或共享变量

WaitGroup 只解决“是否完成”,不解决“结果是什么”。想汇总多个 goroutine 的返回值,不能只靠它。

典型组合方式:

  • chan 接收每个 goroutine 的输出(推荐,天然并发安全)
  • 用互斥锁 + 全局 slice/map 收集结果(需注意初始化和锁粒度)
  • 避免在 goroutine 中直接向未加锁的公共 slice append,会引发 panic 或数据丢失

例如安全收集字符串结果:

ch := make(chan string, len(jobs)) for _, j := range jobs {     wg.Add(1)     go func(job string) {         defer wg.Done()         ch <- processResult(job)     }(j) } go func() {     wg.Wait()     close(ch) }() for res := range ch {     results = append(results, res) }

WaitGroup 不处理 panic,goroutine 崩溃会导致 Wait 永久阻塞

如果某个 goroutine 执行中 panic,且没 recover,Done() 就不会执行,Wait() 将永远卡住 —— 这是最隐蔽的死锁来源之一。

  • 务必在 goroutine 内部用 defer func(){if r:=recover();r!=nil{}}() 包裹业务逻辑
  • 或者统一用封装函数包装 goroutine 启动,自动 recover 并记录日志
  • 开发阶段可启用 GODEBUG=asyncpreemptoff=1 辅助定位长时间阻塞点(非必需,但有用)

没有 recover 的 goroutine 看似“完成了”,实则 Done() 被跳过,计数器永远不归零。

text=ZqhQzanResources