06 | WaitGroup:协同等待,任务编排利器
晁岳攀
该思维导图由 AI 生成,仅供参考
你好,我是鸟窝。
WaitGroup,我们以前都多多少少学习过,或者是使用过。其实,WaitGroup 很简单,就是 package sync 用来做任务编排的一个并发原语。它要解决的就是并发 - 等待的问题:现在有一个 goroutine A 在检查点(checkpoint)等待一组 goroutine 全部完成,如果在执行任务的这些 goroutine 还没全部完成,那么 goroutine A 就会阻塞在检查点,直到所有 goroutine 都完成后才能继续执行。
我们来看一个使用 WaitGroup 的场景。
比如,我们要完成一个大的任务,需要使用并行的 goroutine 执行三个小任务,只有这三个小任务都完成,我们才能去执行后面的任务。如果通过轮询的方式定时询问三个小任务是否完成,会存在两个问题:一是,性能比较低,因为三个小任务可能早就完成了,却要等很长时间才被轮询到;二是,会有很多无谓的轮询,空耗 CPU 资源。
那么,这个时候使用 WaitGroup 并发原语就比较有效了,它可以阻塞等待的 goroutine。等到三个小任务都完成了,再即时唤醒它们。
其实,很多操作系统和编程语言都提供了类似的并发原语。比如,Linux 中的 barrier、Pthread(POSIX 线程)中的 barrier、C++ 中的 std::barrier、Java 中的 CyclicBarrier 和 CountDownLatch 等。由此可见,这个并发原语还是一个非常基础的并发类型。所以,我们要认真掌握今天的内容,这样就可以举一反三,轻松应对其他场景下的需求了。
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
WaitGroup是Go语言中用于协同等待和任务编排的并发原语,通过使用它可以有效地阻塞等待一组goroutine全部完成,避免低效性和CPU资源浪费。本文深入浅出地介绍了WaitGroup的基本用法和内部实现逻辑,包括Add、Done和Wait方法的具体实现。同时,强调了在使用WaitGroup时需要避免的三个常见错误,以及相关的异常检查逻辑。常见问题包括计数器设置为负值、不期望的Add时机以及前一个Wait还没结束就重用WaitGroup。此外,文章还介绍了noCopy字段的作用,它是一个通用的计数技术,用于帮助vet检查数据结构的复制使用情况。通过学习本文,读者可以快速了解WaitGroup的基本概念和使用方法,以及避免在开发中常见的误用情况。文章还提供了关于WaitGroup的知识地图,方便读者复习。总体而言,本文对WaitGroup进行了全面而深入的解析,适合对并发编程感兴趣的读者阅读学习。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 并发编程实战课》,新⼈⾸单¥59
《Go 并发编程实战课》,新⼈⾸单¥59
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(29)
- 最新
- 精选
- Dragon Frog老师好!接 linxs 同学的提问,我觉得他是一开始没表述清楚问题,我之前也有类似的疑问,后来仔细想了想我是这么理解这个问题的,也想请教老师看理解的 -------------------------------------------------- 为什么32bit系统的处理上,state1的元素排列和64bit的不同呢 64bit : waiter,counter,sem 32bit : sem,waiter,counter ------------------------------------------------------ 首先要理解的是**内存对齐**,32 位机和 64 位机的差别在于每次读取的块大小不同,前者一次读取 4 字节的块,后者一次读取 8 字节的块。 `WaitGroup` 的大小是 12 字节,接下来我声明了一个 `var wg sync.WaitGroup`,假设此处 wg 的内存地址是 0xc420016240,此时这个地址是 64bit 对齐的,因此这里的重点是**不论是 32 位机器还是 64 位机器,state1 的元素排列都是 `waiter,counter,sem`**。wg 的地址空间是 `0xc420016240~0xc42001624c`,因此如果此时是 64 位机的话还有4字节的空间可以分配给其他大小合适的变量。那此时 state1 的排列能不能是 `sem,waiter,counter` 呢?不能,因为 64 bit 值的原子操作必须 64 bit 对齐。 对于 32 位机器就会有一种**特殊情况**,那就是 wg 的内存地址起始被分配到了 0xc420016244,此时这个地址不是 64 bit 对齐的,因此这个时候排列变成了 `sem,waiter,counter`,这样的话,`waiter` 的起始地址变成了 0xc420016248,可以使用 64 bit 值的原子操作。
作者回复: 没错,你理解的对
2020-11-011019 - 橙子888issue 12813 按照 defer 后进先出的原则,Done 一定会在 Add 之前执行吧,为啥是“可能”呢?
作者回复: Done是在另外的goroutine执行的。保证不了先后顺序
2020-10-2326 - 寻风老师你好,我想问一下,为啥64位的int要保证原子操作就一定要64位对齐呢,那么为啥要这样规定呢?之前看到atomic文档后面说了一句就是就是说有部分32位处理器需要使用者自行对齐来保证atomic包中方法的正确性,是不是就是因为waitgroup用了atomic包的东西,为了保证atomic使用的正确才有这样的规定。 atomic文档的内容是:On ARM, x86-32, and 32-bit MIPS, it is the caller’s responsibility to arrange for 64-bit alignment of 64-bit words accessed atomically. The first word in a variable or in an allocated struct, array, or slice can be relied upon to be 64-bit aligned.
作者回复: 对,保证指令在这些cpu架构上不会panic
2021-04-024 - moooofly没理解错的话,waiter 数量对应的应该是调用 Wait() 的 goroutine 的数量吧,文中的示例代码都只是在 main goroutine 中调用一次,所以 waiter 数量都只是 1 ,没错吧
作者回复: 对
2020-10-264 - test32位/64位对齐的思考: 如果内存地址不是64位对齐,则让seman填充第一个32位,这样子就可以使得后面的state以64位对齐(因为state存储的两个值要同步修改)。
作者回复: Yeah
2021-05-2823 - 一笑淡然老师好,Add() 中,先将delta<<32位,加入counter,是不是counter应该在waiter位前,即 64bit : counter,waiter,,sem 32bit : sem,counter,waiter
作者回复: 主要是保证操作的是一个对齐的64bit,否则可能panic
2021-06-0442 - 蒋巧纯老师好,我想问一下,为什么不在waitgroup中使用32位的原子操作?state1代表的三个值,其实都各占32bit,分离他们并且使用32位的原子操作,不是应该更好理解吗?
作者回复: 因为需要同时原子更新多个值,分开设置就不是原子操作了
2020-12-2922 - 新味道// 阻塞休眠等待 runtime_Semacquire(semap) -------------- 没理解『阻塞休眠等待』的意思,能否再详细讲一下。
作者回复: 这个是运行时的实现,用来阻塞当前goroutine. 它会把当前g放入队列,标记成waiting,让渡m
2020-10-232 - 王麒如果你想要自己定义的数据结构不被复制使用,或者说,不能通过 vet 工具检查出复制使用的报警,就可以通过嵌入 noCopy 这个数据类型来实现。 这里不应该是能通过vet工具检查出复制吗。。看起来怪怪的。
作者回复: vet需要知道你要不要检查复制,nocopy给vet提示:请检查
2021-03-311 - linxs为什么32bit系统的处理上,state1的元素排列和64bit的不同呢 64bit : waiter,counter,sem 32bit : sem,waiter,counter
作者回复: 因为32bit上是32bit的对齐的,state1 地址不一定正好是8byte对齐
2020-10-2631
收起评论