Once
总结
- Once只对外提供了do方法
- 结构体由done uint32和m Mutex组成,done代表执行的次数,m是
获取次数(并发读写会影响结果,所以加锁)
的时候加锁处理 - 无论是否执行成功,都会deferatomic.StoreUint32(&o.done, 1) 加1操作
- 如果当前done是0,则会执行,否则直接返回
Go 语言在标准库的 sync
同步包中还提供了 Once
语义,它的主要功能其实也很好理解,保证在 Go 程序运行期间 Once
对应的某段代码只会执行一次。
在如下所示的代码中,Do
方法中传入的函数只会被执行一次,也就是我们在运行如下所示的代码时只会看见一次 only once
的输出结果:
func main() {
o := &sync.Once{}
for i := 0; i < 10; i++ {
o.Do(func() {
fmt.Println("only once")
})
}
}
$ go run main.go
only once
作为 sync
包中的结构体,Once
有着非常简单的数据结构,每一个 Once
结构体中都只包含一个用于标识代码块是否被执行过的 done
以及一个互斥锁 Mutex
:
type Once struct {
done uint32
m Mutex
}
Once
结构体对外唯一暴露的方法就是 Do
,该方法会接受一个入参为空的函数,如果使用 atomic.LoadUint32
检查到已经执行过函数了,就会直接返回,否则就会进入 doSlow
运行传入的函数:
func (o *Once) Do(f func()) {
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)
f()
}
}
doSlow
的实现也非常简单,我们先为当前的 Goroutine 获取互斥锁,然后通过 defer
关键字将 done
成员变量设置成 1
并运行传入的函数,无论当前函数是正常运行还是抛出 panic
,当前方法都会将 done
设置成 1
保证函数不会执行第二次。