01
介绍
在 Go 语言中,标准库 sync 包的 WaitGroup 用于父线程(goroutine)等待一组子线程(goroutine)结束,如果正在执行的一组子线程还没有全部结束,父线程阻塞在检查点,直到所有子线程全部结束才可以继续执行。
02
基本使用
WaitGroup 提供了 3 个方法,Add、Done 和 Wait,下面分别介绍一下这 3 个方法:
- Add(delta int):用于设置 WaitGroup 计数器的值。
- Done():用于将 WaitGroup 计数器的值减 1.
- Wait():
用于阻塞调用 Wait() 方法的 goroutine,直到 WaitGroup 计数器的值为 0。
我们通过并发计数的代码示例,演示 WaitGroup 的 3 个方法的基本使用:
阅读并发计数代码,我们可以发现程序通过启动 10 个 goroutine,并发执行计数。
- 第 20 行,声明 WaitGroup 变量,初始值为 0。
- 第 21 行,设置 WaitGroup 计数器的值为 10,因为我们编排 10 个 goroutine 并发执行计数代码。
- 第 24 行,每个 goroutine 执行结束,使用 defer 调用 Done 方法,将 WaitGroup 计数器的值减 1。
- 第 28 行,通过调用 Wait 方法,检查子 goroutine 是否全部结束,决定父 goroutine 是否继续执行。
03
实现原理
type WaitGroup struct { noCopy noCopy state1 [3]uint32 }
阅读源码,可以发现 WaitGroup 包含两个字段,noCopy 字段是用来辅助 vet检查该 WaitGroup 是否是通过 Copy 赋值,state1 字段是用来记录 WaitGroup 的计数器值、信号量和阻塞的 waiter 数量。
WaitGroup 的 Add(delta int) 方法,主要就是操作 state1,传入参数 delta,程序将 delta 的值加到计数器上,delta 的值可以为负值,Done 方法就是调用 Add(-1) 实现的,但是不建议大家传负值使用 Add 方法。
WaitGroup 的 Wait 方法的实现逻辑是,不断检查 state 的值,如果发现 state 的值为 0,说明所有 goroutine 都已经结束,Wait 方法的调用者可以继续执行,如果发现 state 的值不为 0,说明还有 goroutine 没有结束,Wait 方法的调用者需要阻塞。
04
踩坑
WaitGroup 计数器的值必须大于等于 0,如果计数器的值小于 0,会导致程序 panic。所以,我们在使用的时候,不建议给 Add(delta int) 方法的 delta 参数传递负值。并且还必须保证两点,一是设置计数器的值与 goroutine 的数量一致,二是调用 Done 方法的次数与 goroutine 的数量一致。
05
总结
本文开篇先介绍了 WaitGroup 的作用,接着通过并发计数的代码示例,演示了 WaitGroup 的 3 个方法如何使用,然后介绍了 WaitGroup 的 3 个方法的实现原理,最后列举了一个非常容易踩的「坑」。