Golang WaitGroup原理

最近在学习Golang的WaitGroup,但对它的实现原理还不太理解。想请教下:

  1. WaitGroup内部是怎么实现计数和等待机制的?
  2. 它的Add()、Done()和Wait()方法具体是怎么协作的?
  3. 底层使用了哪些同步原语?
  4. 在高并发场景下使用WaitGroup需要注意什么? 希望能结合实际代码示例讲解下原理,谢谢!
2 回复

Golang WaitGroup基于计数器实现并发控制。通过Add()增加计数,Done()减少计数,Wait()阻塞直到计数器归零。内部使用信号量机制实现高效等待。适用于等待一组goroutine完成执行的场景。

更多关于Golang WaitGroup原理的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang 中的 WaitGroupsync 包提供的一种同步原语,用于等待一组 goroutine 完成执行。其核心原理如下:

核心结构

type WaitGroup struct {
    noCopy noCopy
    state1 [3]uint32
}
  • noCopy 防止值拷贝(编译期检查)。
  • state1 存储状态和信号量,包含:
    • 计数器(counter):当前未完成的 goroutine 数量。
    • 等待器数量(waiter):调用 Wait() 的 goroutine 数。
    • 信号量(sema):用于阻塞和唤醒 Wait()

关键方法

  1. Add(delta int)

    • 增加或减少计数器(delta 可为负值)。
    • 若计数器变为 0,唤醒所有等待的 goroutine。
    • 若计数器为负,触发 panic。
  2. Done()

    • 调用 Add(-1),表示一个 goroutine 完成。
  3. Wait()

    • 等待计数器归零。
    • 若计数器不为 0,阻塞当前 goroutine 直至被唤醒。

工作流程

  1. 主 goroutine 调用 Add(n) 设置需等待的 goroutine 数量。
  2. 启动多个 goroutine,每个在结束时调用 Done()
  3. 主 goroutine 调用 Wait() 阻塞,直到所有 goroutine 完成(计数器为 0)。

底层实现

  • 使用原子操作(atomic)保证计数器与等待器的线程安全。
  • 通过信号量(sema)实现 goroutine 的阻塞与唤醒:
    • Wait() 在计数器 >0 时,通过 runtime.semacquire 阻塞。
    • 最后一个 Done()(计数器归零)通过 runtime.semrelease 唤醒所有等待者。

示例代码

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 3; i++ {
        wg.Add(1) // 增加计数器
        go func(id int) {
            defer wg.Done() // 完成后减少计数器
            fmt.Printf("Goroutine %d done\n", id)
        }(i)
    }
    wg.Wait() // 等待所有完成
    fmt.Println("All goroutines finished")
}

注意事项

  • 调用 Add() 必须在启动 goroutine ,避免竞态条件。
  • 严禁复制 WaitGroup(通过 noCopy 保障)。
  • 计数器归零后,WaitGroup 可重复使用(通过 Add() 重置)。

通过原子操作与信号量的高效结合,WaitGroup 以最小开销实现多 goroutine 的同步等待。

回到顶部