gitalk icon indicating copy to clipboard operation
gitalk copied to clipboard

Go sync.Once的三重门

Open smallnest opened this issue 4 years ago • 9 comments

https://colobu.com/2021/05/05/triple-gates-of-sync-Once/

smallnest avatar May 05 '21 12:05 smallnest

首先,第 15 行的 defer atomic.StoreUint32(&o.done, 1) 可以确保执行万第 16 行的 f 才将 done 设置为 1。

typo 执行万 => 执行完

kscooo avatar May 06 '21 16:05 kscooo

麻烦问一下,为什么不用 atomic.CompareAndSwapUint32 方法,替代 atomic.LoadUint32、atomic.StoreUint32 和 Mutex?


好吧,刚看了一眼源码,源码里写的清清楚楚 https://go.googlesource.com/go/+/go1.16.3/src/sync/once.go#42

haozibi avatar May 08 '21 03:05 haozibi

补一个文末点题:

  • 第一重门:原子读 if atomic.LoadUint32(&o.done) == 0 ,只有 f() 未执行结束才需要 sync.Mutex 提供悲观锁的线程安全以及阻塞语义,初始化完成后无锁,性能高。
  • 第二重门:上锁 o.m.Lock() 利用线程同步机制确保并发安全,以及利用锁确保 happens before 的语义,弥补 Go 中原子操作没有 happens before 的缺陷
  • 第三重门:double-check,避免阻塞于锁的协程被唤醒后重复执行 f()

wathenjiang avatar May 12 '21 14:05 wathenjiang

有个问题,if atomic.LoadUint32(&o.done) == 0 能否替换成 if o.done == 0 ,这样虽然会多次进入doSlow, 但是后面大部分时候都不会进入的。这样直接读是否能比原子读提供更好的性能。

fuyuntt avatar Feb 14 '22 10:02 fuyuntt

第六行不用 atomic 也能看到设置的结果呀

zzZfans avatar Sep 30 '22 11:09 zzZfans

我觉得 defer atomic.StoreUint32(&o.done, 1) 是不是也可以替换成 o.done=1。mutex 已经可以保证不会重排,f 执行完再执行这一句了。所以修改成 o.done=1 我觉得也没问题。

ladventure avatar Sep 05 '24 08:09 ladventure

这样的话,Go 可以保证第 15 行 defer atomic.StoreUint32(&o.done, 1) 肯定会在第 16 行 f() 之后执行,
这样就不会出现未初始化完成就将 done 设置为 1 的问题。

见原文第三节有解释。

kscooo avatar Sep 05 '24 10:09 kscooo

我之前也是认为 StoreUint32 是为了其他地方能读到最新值,但是看起来只有 Do 开始那里(第六行)会读这个值,但是那里已经使用 LoadUint32 读取最新值了。

theanarkh avatar Sep 24 '24 16:09 theanarkh

看了一下,StoreUint32 是为了避免函数 f 和设置 done = 1 的乱序执行。

theanarkh avatar Sep 24 '24 16:09 theanarkh