概述
Go语言标准库中的 sync.Once 可以保证 Go 语言程序运行期间某段代码只会执行一次。
下面我们看一段代码:
func main(){
o := &sync.Once{}
for i := 0; i < 10; i++ {
o.Do(func () {
fmt.Println("only once")
})
}
}
运行结果:
only once
这里结果只输出一次,所以显而易见,sync.Once 在全局执行时,只会执行一次。
结构体
每一个 sync.Once 结构体中都只包含一个用于标识代码是否执行过的 done,以及一个互斥锁 sync.Mutex:
type Once struct {
done uint32
m Mutex
}
接口
sync.Once.Do 是 sync.Once 结构体对外唯一暴露的方法,该方法会接收一个入参为空的函数:
- 如果传入的函数已经执行过,会直接返回
- 如果传入的函数没有执行过,会调用 sync.Once.doSlow 执行传入的函数
func (o *Once) Do (f func()) {
if atomic.LoadUint32(&o.done) == 0 {
o.doneSlow(f)
}
}
func (o *Once) doneSlow (f func ()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
sync.Once.doSlow 的核心逻辑
- 为当前 Goroutine 获取互斥锁
- 执行传入的无入参函数
- 运行延迟函数调用,将成员变量 done 更新为 1
- sync.Once 会通过成员变量 done 确保函数不会执行第二次
小结
作为用于保证函数执行次数的 sync.Once 结构体,它使用互斥锁和 sync/atomic 包提供的方法实现了某个函数在程序运行期间只能执行一次的语义。
使用该结构体时需要注意以下问题:
- sync.Once.Do 方法中传入的函数只会被执行一次,哪怕函数中发生了 panic
- 两次调用 sync.Once.Do 方法传入不同的函数只会执行第一次调用传入的函数