在 Go 语言的并发编程中,sync.WaitGroup 无疑是最常用的同步原语之一。从 Go 1.0 开始,它就陪伴着我们处理各种 goroutine 同步场景。

然而,多年来我们一直沿用着固定的使用模式:

var wg sync.WaitGroup
wg.Add(1)
go func() {
    defer wg.Done()
    // 执行业务逻辑
}()
wg.Wait()

这段代码你是否觉得眼熟?没错,这就是我们写了无数遍的模板代码。忘记写 wg.Add(1) 或者忘记 defer wg.Done() 就是一个 BUG,即便你都没有忘记,但这样的重复代码是否让你觉得有些繁琐?

Go 1.25 敏锐地捕捉到了这个痛点,为 WaitGroup 引入了全新的 Go() 方法,让并发编程变得更加简洁和优雅。

传统 WaitGroup 写法

经典使用模式的问题

让我们先看一个典型的例子:

func processItems(items []string) {
    var wg sync.WaitGroup
    for _, item := range items {
        wg.Add(1)
        go func(i string) {
            defer wg.Done()
            process(i)
        }(item)
    }
    wg.Wait()
}

这段代码看起来没有问题,但仔细想想,它存在几个可以改进的地方:

1. 代码冗余

每次启动 goroutine 都要写 wg.Add(1)defer wg.Done() 这样的模板代码。虽然使用 defer 可以确保正确性,但重复的代码增加了编写和阅读的成本。

2. 心智负担

即使是经验丰富的开发者,每次都要记得使用 defer 关键字,确保 Done() 在任何情况下都会被调用。这种重复的决策消耗了不必要的认知资源。

3. 可读性不佳

业务逻辑被同步代码包裹,核心的代码意图不够清晰。

Go() 方法的诞生

什么是 Go() 方法?

Go 1.25 引入的 Go() 方法,本质上是一个封装了 Add(1)Done() 的辅助方法。它的签名非常简单:

func (wg *WaitGroup) Go(f func())

使用方法也极其直观:

var wg sync.WaitGroup
wg.Go(func() {
    // 执行业务逻辑
    // 无需手动调用 Add 和 Done
})
wg.Wait()

核心实现原理

Go() 方法的实现简洁而优雅:

func (wg *WaitGroup) Go(f func()) {
    wg.Add(1)
    go func() {
        defer wg.Done()
        f()
    }()
}

看到了吗?它利用 defer 确保 Done() 一定会被调用,即使函数中发生了 panic。这个简单的设计解决了我们之前提到的所有问题。

Go() 方法解决了什么问题

1. 代码更简洁

自动处理同步原语

使用 Go() 方法后,Add(1)Done() 的调用被封装在方法内部,开发者无需关心这些细节:

func processItems(items []string) {
    var wg sync.WaitGroup
    for _, item := range items {
        wg.Go(func() {
            process(item)
        })
    }
    wg.Wait()
}

内置 defer 保证

Go() 内部使用 defer wg.Done(),确保无论函数如何退出,Done() 都会被执行,即使发生 panic。

2. 代码简洁性

减少模板代码

对比一下两种写法:

// 传统写法
wg.Add(1)
go func() {
    defer wg.Done()
    // 业务逻辑
}()

// Go() 方法
wg.Go(func() {
    // 业务逻辑
})

代码行数减少了 40%,可读性显著提升。

3. 意图更清晰

使用 Go() 方法后,代码的意图更加明确:

// 一眼就能看出这是并发执行的任务
wg.Go(func() { fetchUser() })
wg.Go(func() { fetchOrder() })
wg.Go(func() { fetchProduct() })

写在最后

sync.WaitGroupGo() 方法虽然只是一个语法糖,却体现了 Go 语言设计哲学的精髓:

  • 简单优于复杂:用简洁的封装减少模板代码
  • 效率优于形式:让开发者专注于业务逻辑而非同步细节
  • 实用优于理论:解决实际开发中的痛点

学习就是不断进化的过程,进化让编程变得更加美好。