数据结构
为什么需要 WaitGroup?
想象一个场景:我们有一个用户画像服务,当一个请求到来时,需要
- 从 request 里解析出 user_id 和 画像维度参数
- 根据 user_id 从 ABCDE 五个子服务(数据库服务、存储服务、rpc服务等)拉取不同维度的信息
- 将读取的信息进行整合,返回给调用方
假设 ABCDE 五个服务的响应时间 p99 是 20~50ms 之间。如果我们顺序调用 ABCDE 读取信息,不考虑数据整合消耗时间,服务端整体响应时间 p99 是:
sum(A, B, C, D, E) => [100ms, 250ms]
先不说业务上能不能接受,响应时间上显然有很大的优化空间。最直观的优化方向就是,取数逻辑的总时间消耗:
sum(A, B, C, D, E) -> max(A, B, C, D, E)
具体到 coding 上,我们需要并行调用 ABCDE 五个子服务,待调用全部返回以后,进行数据整合。如何保障全部返回呢?
此时,sync.WaitGroup 闪耀登场。
sync.WaitGroup
sync.WaitGroup是为了解决任务编排而出现的, 主要就是解决并发-等待问题, 因此在真正编写过程中也很常用。
WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量,每一次执行Add都会增加线程组的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。
如果你有多个goroutine并发执行某项操作,但是下方的代码需要等待这些并发的操作执行完成之后才能够执行,那么你可以使用sync.WaitGroup,因为它比Sleep更加有效,并且稳定。
WaitGroup 位于golang sync 包下,对应的类声明中包含了几个核心字段:
noCopy:这是防拷贝标识,标记了WaitGroup不应该用于值传递state1:这是WaitGroup的核心字段,是一个无符号的64位整数,高32位是WaitGroup中并发计数器的数值,即当前WaitGroup.Add与WaitGroup.Done之间的差值;低 32 位标识了,当前有多少goroutine因WaitGroup.Wait操作而处于阻塞态,陷入阻塞态的原因是因为计数器的值没有清零,即state1字段高 32 位是一个正值state2:用于阻塞和唤醒goroutine的信号量
sync.WaitGroup内部维护着一个计数器,计数器的值可以增加和减少。例如当我们启动了N 个并发任务时,就将计数器值增加N。每个任务完成时通过调用Done()方法将计数器减1。通过调用Wait()来等待并发任务执行完,当计数器值为0时,表示所有并发任务已经完成。
下面看一个简单使用的例子
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func foo(){
defer wg.Done()
fmt.Println("foo")
time.Sleep(time.Second*2)
fmt.Println("foo end")
}
func bar(){
defer wg.Done()
fmt.Println("bar")
time.Sleep(time.Second*2)
fmt.Println("bar end")
}
func main(){
start:=time.Now()
wg.Add(2) //括号里面的值为执行并发程序的个数
go foo()
go bar()
wg.Wait() //通过调用Wait()来等待并发任务执行完,当计数器值为0时,表示所有并发任务已经完成。
fmt.Println("程序结束,运行时间为",time.Now().Sub(start))
}
程序比较简单。
可以看一下下面的表格。
| 方法名 | 功能 |
|---|---|
| (wg * WaitGroup) Add(delta int) | 计数器+delta |
| (wg *WaitGroup) Done() | 计数器-1 |
| (wg *WaitGroup) Wait() | 阻塞直到计数器变为0 |
Add
Add主要是对state1字段中的计数值部分进行操作, 步骤如下:
- 将参数
delta左移32位 - 将值加到计数值上(
state1[1]), 这个值是可负可正
Done
Done实际上就是调用Add(-1)
而检测到-1之后的计数值为0时, 通过信号量唤醒正在wait的阻塞协程
Wait
调用wait时, 会将waiter+1, 然后将自身加入到等待队列中并阻塞, 等待Done时的唤醒
总结
这里暂时列举大部分,先留个坑,后期会把源码分析的内容也放到这个博客上面来分享一下。
参考
zhuanlan.zhihu.com/p/632190680
你真的会用sync.WaitGroup吗?zhuanlan.zhihu.com/p/75441551