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

Go语言并发编程入门(使用sync.WaitGroup等待goroutine完成)

Go语言 中,goroutine 是实现并发的核心机制。它轻量、高效,可以轻松启动成千上万个并发任务。然而,当我们启动多个 goroutine 后,如何确保主程序等待它们全部执行完毕再退出呢?这就需要用到 sync.WaitGroup —— 一个用于goroutine并发控制的标准同步原语。

Go语言并发编程入门(使用sync.WaitGroup等待goroutine完成) Go语言 sync.WaitGroup goroutine并发控制 Go并发编程 第1张

什么是 sync.WaitGroup?

sync.WaitGroup 是 Go 标准库 sync 包中的一个结构体,用于等待一组 goroutine 完成。它内部维护一个计数器,通过以下三个方法操作:

  • Add(delta int):增加或减少计数器的值(通常在启动 goroutine 前调用)
  • Done():将计数器减 1(通常在 goroutine 结束时调用)
  • Wait():阻塞当前 goroutine,直到计数器变为 0

基本使用示例

下面是一个简单的例子,展示如何使用 sync.WaitGroup 等待两个 goroutine 执行完毕:

package mainimport (    "fmt"    "sync"    "time")func worker(id int, wg *sync.WaitGroup) {    defer wg.Done() // goroutine 结束时自动调用 Done()    fmt.Printf("Worker %d 开始工作\n", id)    time.Sleep(2 * time.Second) // 模拟耗时任务    fmt.Printf("Worker %d 工作完成\n", id)}func main() {    var wg sync.WaitGroup    // 启动 2 个 goroutine    for i := 1; i <= 2; i++ {        wg.Add(1) // 每启动一个 goroutine,计数器 +1        go worker(i, &wg)    }    wg.Wait() // 主 goroutine 在这里等待,直到计数器归零    fmt.Println("所有任务已完成!")}

运行这段代码,你会看到类似如下输出:

Worker 1 开始工作Worker 2 开始工作Worker 1 工作完成Worker 2 工作完成所有任务已完成!

关键注意事项

  1. 必须传指针:因为 WaitGroup 的状态需要在多个 goroutine 间共享,所以应传递 *sync.WaitGroup 指针。
  2. 避免在循环中直接使用循环变量:如果在 goroutine 中直接引用循环变量(如 i),可能会出现竞态条件。建议将变量作为参数传入函数。
  3. Add() 调用时机:通常在启动 goroutine 之前调用 Add(1),而不是在 goroutine 内部调用,以避免竞态。
  4. 不要对 WaitGroup 复用:一旦 Wait() 返回,不要再次使用同一个 WaitGroup,除非重新初始化。

常见错误示例

下面是一个常见的错误写法:

// 错误!Add() 在 goroutine 内部调用,可能导致主 goroutine 先执行 Wait() 而计数器仍为 0for i := 1; i <= 2; i++ {    go func() {        wg.Add(1)        defer wg.Done()        // ... 工作    }()}wg.Wait()

这种写法存在竞态条件:主 goroutine 可能在子 goroutine 调用 Add(1) 之前就执行了 Wait(),导致程序提前退出。

总结

sync.WaitGroupGo并发编程 中最常用的同步工具之一,特别适合用于“启动一批任务,然后等待它们全部完成”的场景。掌握它,是学习 Go语言 并发模型的重要一步。

记住三个关键词:AddDoneWait,配合使用即可优雅地管理多个 goroutine 的生命周期。

希望这篇教程能帮助你理解 sync.WaitGroup 的基本用法和最佳实践。如果你刚开始学习 goroutine并发控制,不妨动手运行上面的代码,加深理解!