Once
sync.Once
可以保证在 Go 程序运行期间的某段代码只会执行一次。
func main() {
o := sync.Once{}
for i := 0; i < 10; i++ {
o.Do(func() {
fmt.Println("only once")
})
}
}
// Once is an object that will perform exactly one action.
//
// A Once must not be copied after first use.
//
// In the terminology of the Go memory model,
// the return from f “synchronizes before”
// the return from any call of once.Do(f).
type Once struct {
// done indicates whether the action has been performed.
// It is first in the struct because it is used in the hot path.
// The hot path is inlined at every call site.
// Placing done first allows more compact instructions on some architectures (amd64/386),
// and fewer instructions (to calculate offset) on other architectures.
done uint32
m Mutex
}
done
:done只会被修改一次,所以Once不能重用。mutex
:加锁保护,防并发。
sync.Once.Do
是sync.Once
结构体对外唯一暴露的方法,该方法会接收一个入参为空的函数:- 如果传入的函数已经执行过,会直接返回。
- 如果传入的函数没有执行过,会调用
sync.Once.doSlow
执行传入的函数。
Do
func (o *Once) Do(f func()) {
// Note: Here is an incorrect implementation of Do:
//
// if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
// f()
// }
//
// Do guarantees that when it returns, f has finished.
// This implementation would not implement that guarantee:
// given two simultaneous calls, the winner of the cas would
// call f, and the second would return immediately, without
// waiting for the first's call to f to complete.
// This is why the slow path falls back to a mutex, and why
// the atomic.StoreUint32 must be delayed until after f returns.
// 没有执行过,执行doslow函数
if atomic.LoadUint32(&o.done) == 0 {
// Outlined slow-path to allow inlining of the fast-path.
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
// 加锁保护,确保一次只有一个gorountine执行
o.m.Lock()
defer o.m.Unlock()
// 双重校验,确保done == 0的时候才会执行
if o.done == 0 {
// 这个defer会比解锁操作先完成
defer atomic.StoreUint32(&o.done, 1)
// 执行自定义函数
f()
}
}
小结
作为用于保证函数执行次数的 sync.Once
结构体,它使用互斥锁和 sync/atomic 包提供的方法实现了某个函数在程序运行期间只能执行一次的语义。
在使用该结构体时,我们也需要注意以下的问题:
sync.Once.Do
方法中传入的函数只会被执行一次,哪怕函数中发生了panic
。- 两次调用
sync.Once.Do
方法传入不同的函数只会执行第一次调传入的函数。