Go 并发编程实战课
晁岳攀(鸟窝)
前微博技术专家,知名微服务框架 rpcx 作者
25635 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 22 讲
Go 并发编程实战课
15
15
1.0x
00:00/00:00
登录|注册

06 | WaitGroup:协同等待,任务编排利器

Done方法调用过多
调用Add时传递负数
state1
noCopy
Kubernetes
Docker
辅助vet检查
前一个Wait还没结束就重用WaitGroup
不期望的Add时机
计数器设置为负值
Wait方法
Done方法
Add方法
数据结构
Wait
Done
Add
思考题
总结
流行的Go开发项目中的坑
noCopy
常见错误
实现
基本用法
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
立即购买
登录 后留言

全部留言(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-01
    10
    19
  • 橙子888
    issue 12813 按照 defer 后进先出的原则,Done 一定会在 Add 之前执行吧,为啥是“可能”呢?

    作者回复: Done是在另外的goroutine执行的。保证不了先后顺序

    2020-10-23
    2
    6
  • 寻风
    老师你好,我想问一下,为啥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-02
    4
  • moooofly
    没理解错的话,waiter 数量对应的应该是调用 Wait() 的 goroutine 的数量吧,文中的示例代码都只是在 main goroutine 中调用一次,所以 waiter 数量都只是 1 ,没错吧

    作者回复: 对

    2020-10-26
    4
  • test
    32位/64位对齐的思考: 如果内存地址不是64位对齐,则让seman填充第一个32位,这样子就可以使得后面的state以64位对齐(因为state存储的两个值要同步修改)。

    作者回复: Yeah

    2021-05-28
    2
    3
  • 一笑淡然
    老师好,Add() 中,先将delta<<32位,加入counter,是不是counter应该在waiter位前,即 64bit : counter,waiter,,sem 32bit : sem,counter,waiter

    作者回复: 主要是保证操作的是一个对齐的64bit,否则可能panic

    2021-06-04
    4
    2
  • 蒋巧纯
    老师好,我想问一下,为什么不在waitgroup中使用32位的原子操作?state1代表的三个值,其实都各占32bit,分离他们并且使用32位的原子操作,不是应该更好理解吗?

    作者回复: 因为需要同时原子更新多个值,分开设置就不是原子操作了

    2020-12-29
    2
    2
  • 新味道
    // 阻塞休眠等待 runtime_Semacquire(semap) -------------- 没理解『阻塞休眠等待』的意思,能否再详细讲一下。

    作者回复: 这个是运行时的实现,用来阻塞当前goroutine. 它会把当前g放入队列,标记成waiting,让渡m

    2020-10-23
    2
  • 王麒
    如果你想要自己定义的数据结构不被复制使用,或者说,不能通过 vet 工具检查出复制使用的报警,就可以通过嵌入 noCopy 这个数据类型来实现。 这里不应该是能通过vet工具检查出复制吗。。看起来怪怪的。

    作者回复: vet需要知道你要不要检查复制,nocopy给vet提示:请检查

    2021-03-31
    1
  • linxs
    为什么32bit系统的处理上,state1的元素排列和64bit的不同呢 64bit : waiter,counter,sem 32bit : sem,waiter,counter

    作者回复: 因为32bit上是32bit的对齐的,state1 地址不一定正好是8byte对齐

    2020-10-26
    3
    1
收起评论
显示
设置
留言
29
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部