Open LLLeon opened 3 years ago
在群里看到有人讨论 Once 的实现细节,也看了下源码,记录一下。
Once 的一般使用场景:确保初始化操作只执行一次。
直接看源码吧:
package sync import ( "sync/atomic" ) type Once struct { done uint32 m Mutex } func (o *Once) Do(f func()) { // 下面是一个 Do 的错误实现: // // if atomic.CompareAndSwapUint32(&o.done, 0, 1) { // f() // } // // Do 要确保它返回时,f 已经执行完毕。 // 而这个实现不能确保这一点。考虑这个情况: // 1. 当有多个 goroutine 来执行 Do 时,goroutine1 判断 o.done 为 0, // 将其修改为 1 后开始执行 f。 // 2. goroutine2 判断 o.done 为 1,直接返回,此时 f 中的资源不一定初始化完毕。 // 3. 如果此时 goroutine2 就开始使用 f 未初始化完毕的资源,可能会出现意外情况。 if atomic.LoadUint32(&o.done) == 0 { o.doSlow(f) } } // 接上面,这就是为什么 doSlow 里面使用了 mutex。考虑这个情况: // 1. goroutine1 原子加载 o.done,此时值为 0,开始执行 doSlow。goroutine1 先获取到锁, // 执行 f 但未返回。 // 2. goroutine2 此时也原子加载 o.done,由于 goroutine1 还未修改其值,所以还为 0。 // 也执行 doSlow,但获取不到锁,阻塞在这里。 // 3. goroutine1 执行完毕 f,返回前将 o.done 设为 1 并释放锁。 // 4. goroutine 2 获取到锁,判断 o.done 为 1,直接返回。此时 f 中的资源已初始化完毕,可以使用。 func (o *Once) doSlow(f func()) { o.m.Lock() defer o.m.Unlock() if o.done == 0 { // 由于要确保 f 执行完毕后(不管成功或失败)再修改 o.done 的值,所以使用 defer。 // 后续对 Do 的调用,都不会执行 f。 defer atomic.StoreUint32(&o.done, 1) f() } }
在群里看到有人讨论 Once 的实现细节,也看了下源码,记录一下。
Once 的一般使用场景:确保初始化操作只执行一次。
直接看源码吧: