-
Notifications
You must be signed in to change notification settings - Fork 4
Open
Labels
SourceCodeAnalysis_GoGolang source code analysis notes.Golang source code analysis notes.
Description
在群里看到有人讨论 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()
}
}Metadata
Metadata
Assignees
Labels
SourceCodeAnalysis_GoGolang source code analysis notes.Golang source code analysis notes.