Go 语言标准库中 sync.Once
可以保证在 Go 程序运行期间的某段代码只会执行一次。
其原理也很简单:用一个数字表示是否执行过 + 锁保证并发场景下只执行一次。后续再次执行时,检查该变量即可。
type Once struct {
done uint32 // 用来标识代码块是否执行过
m Mutex // 互斥锁
}
func (o *Once) Do(f func()) {
// done==0,说明没执行过,进入慢路径
if atomic.LoadUint32(&o.done) == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 { // 双重检查。因为可能有多个协程进入慢路径。第一个协程执行完毕后,后面的协程按顺序还能获取锁,此时再判断一次
defer atomic.StoreUint32(&o.done, 1) // 执行成功后将 done 变量修改为1
f()
}
}
在使用该结构体时,我们也需要注意以下的问题:
sync.Once.Do
方法中传入的函数只会被执行一次,哪怕函数中发生了panic
- 两次调用
sync.Once.Do
方法传入不同的函数只会执行第一次调传入的函数