31 | sync.WaitGroup和sync.Once
该思维导图由 AI 生成,仅供参考
- 深入了解
- 翻译
- 解释
- 总结
`sync`包中的`WaitGroup`类型和`Once`类型都是Go语言中重要的同步工具。`WaitGroup`类型适用于实现一对多的goroutine协作流程,而`Once`类型则保证传入的参数函数只执行一次。`WaitGroup`类型使用原子操作来管理计数器,需要注意避免计数器值小于0或在调用`Wait`方法的同时并发增加计数器值。相比之下,`Once`类型更简单,只有一个`Do`方法,保证参数函数只执行一次,但可能导致阻塞。两者都是高层次的同步工具,基于基本的通用工具实现特定功能。读者在使用`WaitGroup`值实现一对多的goroutine协作流程时,需要考虑如何让分发子任务的goroutine获得各个子任务的具体执行结果。这篇文章深入解析了`WaitGroup`和`Once`类型的使用方式和特点,适合对并发编程感兴趣的读者阅读学习。
《Go 语言核心 36 讲》,新⼈⾸单¥59
全部留言(41)
- 最新
- 精选
- liangjf“双重检查” 貌似也并不是完全安全的吧,像c++11那样加入内存屏障才是真正线性安全的。go有这类接口吗
作者回复: Go语言底层内置了内存屏障。它的好处就是不用像C++那样什么都需要自己搞。
2019-02-26220 - 唐大少在路上。。。个人感觉Once里面的逻辑设计得不够简洁,既然目的就是只要能够拿到once的锁的gorountine就会消费掉这个once,那其实直接在Do方法的最开始用if atomic.CompareAndSwapUint32(&o.done, 0,1)不就行了,连锁都不用。 还请老师指正,哈哈
作者回复: 这样不行啊,它还得执行你给它的函数啊。怎么能在没执行函数之前就把 done 变成 1 呢,对吧。但如果是在执行之后 swap,那又太晚了,有可能出现重复执行函数的情况。 所以 Once 中才有两个执行路径,一个是仅包含原子操作的快路径,另一个是真正准备执行函数的慢路径。这样才可以兼顾多种情况,让总体性能更优。
2019-10-24317 - only可不可以把 sync.once 理解为单例模式,比如连接数据库只需要连接一次,把连接数据库的代码实在once.do()里面
作者回复: 它跟单例模式还不太一样。单例模式指的是某类结构的唯一实例,而 once 指的是对某段代码的唯一一次执行。它们的维度不一样。 连接数据库的代码其实不太适合放到 Do 里面执行,或者说不太恰当。初始化数据库链接的代码可以放到里面。而,断链重连的机制也应该在其中。
2019-12-1010 - moonfox请问一下,在 sync.Once的源码里, doSlow()方法中,已经用了o.m.Lock(),什么写入o.done=1的时候,还要用原子写入呢?
作者回复: 这是两码事啊,原子操作还有一个作用是保证被操作值的完整性。比如,done字段的值要么是0要么是1。别忘了,done字段的值是由32个比特位组成的。如果在修改值的过程中(还没改完),其他的代码在读取它,那岂不是会读到一个非0非1的值吗?(这是一个小概率问题,但是万一出了错,复现都没法复现,排查起来就太困难了,所以恰恰需要极力避免) 就像源码中的 if atomic.LoadUint32(&o.done) == 0 { 这里。 这行代码可没有锁的加持啊,它可不管另一个goroutine执行到doSlow函数中的哪一步了。另外,对一个值的原子操作必须全面覆盖(如果用,就都要用原子操作)。
2021-05-099 - 超大叮当当sync.Once 不用 Mutex ,直接用 atomic.CompareAndSwapUint32 函数也可以安全吧?
作者回复: 原子操作是CPU级别的互斥,而且防中断。但是支持的数据类型很少,而且并不灵活。所以如果是对代码块进行保护,还需要用锁。
2019-03-136 - 1287没理解使用once和自己只调用一次有什么区别,类似初始化的操作,我在程序执行前写个init也是只执行一次吧,求教
作者回复: 同一个 sync.Once 实例的 Do 方法只会被有效调用一次。init 函数是在当前程序中只会被调用一次,而且它的作用域是代码包。
2020-05-2523 - 窗外go func () { wg.Done() fmt.Println("send complete") }() 老师,为什么在Done()后的代码就不会被执行呢?
作者回复: 你在后面 wg.Wait() 了吗?
2019-11-0323 - 手指饼干请问老师,如下deferFunc为什么要用func包装起来,直接使用defer deferFunc()不可以吗? func addNum(numP *int32, id, max int32, deferFunc func()) { defer func() { deferFunc() }() //... }
作者回复: 你那么写也可以,我弄一坨只是想引起你们的注意。在不影响程序功能和运行效率的前提下,我会在程序里尽量多展示几种写法。
2019-10-1023 - Laughing子任务的结果应该用通道来传递吧。另外once的应用场景还是没有理解。郝大能简单说一下么?
作者回复: 可以通过通道,但这就不是wg的作用范围了。once一般是执行只应该执行一次的任务,比如初始化连接池等等。你可以在go源码里搜一下,用的地方还是不少的。
2018-10-303 - 罗峰老师,你好,waitgroup的计数周期这个概念是自创的吗?使用上感觉 只要 add操作在wait语句之前执行就可以,使用个例子: for { select { case <- cancel: break; case <- taskqueue: go func { wg.add(1) .... defer wg.done() } } } wg.wait()
作者回复: WaitGroup与操作系统的信号灯异曲同工。 必须要有 wg.Done() 啊,否则计数就无法归零,wg.Wait() 也就无法消除阻塞。
2021-01-222