Go并发编程(六) 深入理解 WaitGroup
注:本文已发布超过一年,请注意您所使用工具的相关版本是否适用
本系列为 Go 进阶训练营 笔记,访问 博客: Go进阶训练营, 即可查看当前更新进度,部分文章篇幅较长,使用 PC 大屏浏览体验更佳。
在前面的几篇文章中我们或多或少都用到了 WaitGroup 来等待多个 goroutine 执行结束,今天我们来深入学习一下
案例
WaitGroup
可以解决一个 goroutine 等待多个 goroutine 同时结束的场景,这个比较常见的场景就是例如 后端 worker 启动了多个消费者干活,还有爬虫并发爬取数据,多线程下载等等。
我们这里模拟一个 worker 的例子
1 |
|
源码分析
1 |
|
WaitGroup
结构十分简单,由 nocopy
和 state1
两个字段组成,其中 nocopy
是用来防止复制的
1 |
|
由于嵌入了 nocopy
所以在执行 go vet
时如果检查到 WaitGroup
被复制了就会报错。这样可以一定程度上保证 WaitGroup
不被复制,对了直接 go run 是不会有错误的,所以我们代码 push 之前都会强制要求进行 lint 检查,在 ci/cd 阶段也需要先进行 lint 检查,避免出现这种类似的错误。
1 |
|
state1
的设计非常巧妙,这是一个是十二字节的数据,这里面主要包含两大块,counter 占用了 8 字节用于计数,sema 占用 4 字节用做信号量
为什么要这么搞呢?直接用两个字段一个表示 counter,一个表示 sema 不行么?
不行,我们看看注释里面怎么写的。
// 64-bit value: high 32 bits are counter, low 32 bits are waiter count. > // 64-bit atomic operations require 64-bit alignment, but 32-bit > // compilers do not ensure it. So we allocate 12 bytes and then use > // the aligned 8 bytes in them as state, and the other 4 as storage > // for the sema.
这段话的关键点在于,在做 64 位的原子操作的时候必须要保证 64 位(8 字节)对齐,如果没有对齐的就会有问题,但是 32 位的编译器并不能保证 64 位对齐所以这里用一个 12 字节的 state1 字段来存储这两个状态,然后根据是否 8 字节对齐选择不同的保存方式。
这个操作巧妙在哪里呢?
- 如果是 64 位的机器那肯定是 8 字节对齐了的,所以是上面第一种方式
- 如果在 32 位的机器上
- 如果恰好 8 字节对齐了,那么也是第一种方式取前面的 8 字节数据
- 如果是没有对齐,但是 32 位 4 字节是对齐了的,所以我们只需要后移四个字节,那么就 8 字节对齐了,所以是第二种方式
所以通过 sema 信号量这四个字节的位置不同,保证了 counter 这个字段无论在 32 位还是 64 为机器上都是 8 字节对齐的,后续做 64 位原子操作的时候就没问题了。
这个实现是在 state
方法实现的
1 |
|
state
方法返回 counter 和信号量,通过 uintptr(unsafe.Pointer(&wg.state1))%8 == 0
来判断是否 8 字节对齐
Add
1 |
|
Wait
wait 主要就是等待其他的 goroutine 完事之后唤醒
1 |
|
Done
这个只是 add 的简单封装
1 |
|
总结
WaitGroup
可以用于一个 goroutine 等待多个 goroutine 干活完成,也可以多个 goroutine 等待一个 goroutine 干活完成,是一个多对多的关系- 多个等待一个的典型案例是 singleflight,这个在后面将微服务可用性的时候还会再讲到,感兴趣可以看看源码
Add(n>0)
方法应该在启动 goroutine 之前调用,然后在 goroution 内部调用Done
方法WaitGroup
必须在Wait
方法返回之后才能再次使用Done
只是Add
的简单封装,所以实际上是可以通过一次加一个比较大的值减少调用,或者达到快速唤醒的目的。
参考文献
- https://pkg.go.dev/sync 官网文档,必读
- Go 语言设计与实现-6.2 同步原语与锁 这本书值得一看
- Go by Example: WaitGroups
- https://golang.org/issues/8005#issuecomment-190753527
- Golang 源码系列 sync.waitgroup 源码剖析
关注我获取更新
猜你喜欢
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处,禁止全文转载