在 go 语言中我们可以使用 sync.Once 对象来实现函数方法只执行一次的功能。
简单代码示例
package main
import (
"fmt"
"sync"
)
func main() {
var (
o sync.Once
wg sync.WaitGroup
)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
o.Do(func() {
fmt.Printf("hello %d\n", i)
})
}(i)
}
wg.Wait()
}
输出:
hello 9
不使用 Sync.Once 的结果如下:
hello 9
hello 4
hello 0
hello 1
hello 2
hello 3
hello 6
hello 5
hello 7
hello 8
可以看到, 在使用 sync.Once 的情况下, 只执行一次函数。
解析
通过查看源码,可以看到 Sync.Once 的源码十分简单。 只有一个结构体和2个方法。
type Once struct {
done uint32
m Mutex
}
done 成员用来判断函数是否执行过。
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.
if atomic.LoadUint32(&o.done) == 0 {
// Outlined slow-path to allow inlining of the fast-path.
o.doSlow(f)
}
}
Do 方法的实现是这样的,检查 done 成员,如果为 0,则执行 f 函数。
接下来看看 doSlow 方法,它是一个锁的实现,它的实现如下:
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
doSlow 会执行函数 f,并且在执行完 f 后将 done 成员设置为 1。 在 doSlow 当中使用了互斥锁来保证只会执行一次
小结
Once 保证了传入的函数只会执行一次,这常用在这些场景下:单例模式,配置文件加载,初始化。
Once 是不能复用的,只要执行过一个函数,其他函数就不能再次执行。