前言
Once是一个非常实用的API,它保证了一个事情仅做一次,这个在许多场景非常有用,所以Once也是go提供的为数不多的API之一。
语法基础
Once的语法非常简单,整个API就提供了一个Do函数,Do函数接受的是一个函数对象,通过once.Do可以保证这个事情仅做一次,来看一个demo:
func main() {
var once = sync.Once{}
once.Do(say)
once.Do(say)
once.Do(say)
once.Do(say)
}
func say() {
fmt.Println("say once")
}
输出:
源码实现
// Once is an object that will perform exactly one action.
type Once struct {
m Mutex // 一把锁
done uint32 // 是否已经做过的标记
}
func (o *Once) Do(f func()) {
// 如果已经做过了直接返回,这里使用atmoic保证并发场景下的安全性
if atomic.LoadUint32(&o.done) == 1 {
return
}
// 这里有一个先判断后操作的竞态条件,所以锁了一下
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
// 将状态标记为已完成,这个过程还是在锁内部
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
ps:源码中有一个细节点,如果声明了多个defer,按照定义顺序,倒序执行,这也就是为什么最终把状态置为已完成是发生在锁内部了。 关于Once的内容挺简单的,暂时先介绍这么多。