Golang WaitGroup原理
最近在学习Golang的WaitGroup,但对它的实现原理还不太理解。想请教下:
- WaitGroup内部是怎么实现计数和等待机制的?
- 它的Add()、Done()和Wait()方法具体是怎么协作的?
- 底层使用了哪些同步原语?
- 在高并发场景下使用WaitGroup需要注意什么? 希望能结合实际代码示例讲解下原理,谢谢!
2 回复
Golang WaitGroup基于计数器实现并发控制。通过Add()增加计数,Done()减少计数,Wait()阻塞直到计数器归零。内部使用信号量机制实现高效等待。适用于等待一组goroutine完成执行的场景。
更多关于Golang WaitGroup原理的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang 中的 WaitGroup 是 sync 包提供的一种同步原语,用于等待一组 goroutine 完成执行。其核心原理如下:
核心结构
type WaitGroup struct {
noCopy noCopy
state1 [3]uint32
}
noCopy防止值拷贝(编译期检查)。state1存储状态和信号量,包含:- 计数器(counter):当前未完成的 goroutine 数量。
- 等待器数量(waiter):调用
Wait()的 goroutine 数。 - 信号量(sema):用于阻塞和唤醒
Wait()。
关键方法
-
Add(delta int)
- 增加或减少计数器(delta 可为负值)。
- 若计数器变为 0,唤醒所有等待的 goroutine。
- 若计数器为负,触发 panic。
-
Done()
- 调用
Add(-1),表示一个 goroutine 完成。
- 调用
-
Wait()
- 等待计数器归零。
- 若计数器不为 0,阻塞当前 goroutine 直至被唤醒。
工作流程
- 主 goroutine 调用
Add(n)设置需等待的 goroutine 数量。 - 启动多个 goroutine,每个在结束时调用
Done()。 - 主 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 的同步等待。

