当前位置:首页 > Go > 正文

Go语言并发编程之WaitGroup的误用(新手必看:避免常见WaitGroup陷阱)

Go语言并发编程 中,sync.WaitGroup 是一个非常常用的同步原语,用于等待一组 Goroutine 完成任务。然而,很多初学者甚至有经验的开发者都会在使用 WaitGroup 时犯一些典型错误,导致程序死锁、数据竞争或逻辑错误。

本文将通过通俗易懂的方式,结合代码示例,详细讲解 Go WaitGroup 教程 中常见的误用场景,并提供正确的解决方案,帮助你掌握安全、高效的并发控制技巧。

什么是 WaitGroup?

sync.WaitGroup 是 Go 标准库中的一个结构体,用于协调多个 Goroutine 的执行。它内部维护一个计数器:

  • Add(delta int):增加计数器
  • Done():减少计数器(相当于 Add(-1))
  • Wait():阻塞当前 Goroutine,直到计数器归零
Go语言并发编程之WaitGroup的误用(新手必看:避免常见WaitGroup陷阱) Go语言并发编程 WaitGroup误用 Go WaitGroup教程 Go并发同步 第1张

常见误用一:在 Wait() 之后调用 Add()

这是最常见的错误之一。很多人以为可以在 Wait() 调用后再启动新的 Goroutine 并调用 Add(),但此时如果计数器已经为 0,Wait() 已经返回,再调用 Add() 会导致 panic。

// ❌ 错误示例:Wait() 后再 Add()package mainimport (    "fmt"    "sync")func main() {    var wg sync.WaitGroup    wg.Add(1)    go func() {        defer wg.Done()        fmt.Println("Goroutine 1 done")    }()    wg.Wait() // 此时计数器归零,Wait 返回    // 下面这行会 panic:panic: sync: negative WaitGroup counter    wg.Add(1)    go func() {        defer wg.Done()        fmt.Println("Goroutine 2 done")    }()    wg.Wait()}

正确做法是:确保所有 Add() 调用都在对应的 Goroutine 启动前完成,并且不要在 Wait() 返回后再操作同一个 WaitGroup

常见误用二:Add() 调用位置不当

有些开发者习惯在 Goroutine 内部调用 Add(),这可能导致竞态条件(race condition),因为主 Goroutine 可能在子 Goroutine 调用 Add() 前就执行了 Wait(),从而提前返回。

// ❌ 错误示例:在 Goroutine 内调用 Add()package mainimport (    "fmt"    "sync"    "time")func main() {    var wg sync.WaitGroup    go func() {        wg.Add(1) // 危险!可能在 Wait() 之后才执行        defer wg.Done()        fmt.Println("Task done")    }()    time.Sleep(10 * time.Millisecond) // 试图“等待”Add 执行,不可靠!    wg.Wait() // 可能立即返回(因为计数器仍为0)}

✅ 正确做法:始终在启动 Goroutine **之前** 调用 Add()

// ✅ 正确示例wg.Add(1)go func() {    defer wg.Done()    fmt.Println("Task done")}()wg.Wait()

常见误用三:忘记调用 Done()

如果某个 Goroutine 没有调用 Done()(例如因 panic 提前退出),WaitGroup 的计数器永远不会归零,导致 Wait() 永久阻塞,程序卡死。

// ❌ 错误示例:panic 导致 Done() 未执行wg.Add(1)go func() {    panic("something wrong!")    wg.Done() // 永远不会执行}()wg.Wait() // 永久阻塞!

✅ 解决方案:使用 defer wg.Done() 确保无论函数如何退出,Done() 都会被调用。

总结

Go并发同步 编程中,WaitGroup 是一个强大但需要谨慎使用的工具。牢记以下三点:

  1. 先 Add,再启动 Goroutine
  2. 用 defer Done() 保证执行
  3. 不要在 Wait() 后复用同一个 WaitGroup

掌握这些要点,你就能避免 WaitGroup误用 带来的各种问题,写出更健壮的并发程序。

希望这篇 Go语言并发编程 教程对你有所帮助!欢迎实践并分享你的经验。