go// Once 是一个对象,用于确保只执行一次动作。
//
// 在首次使用后,Once 不应被复制。
//
// 在 Go 内存模型的术语中,
// 从 f 返回的值在任何对 once.Do(f) 的调用的返回之前“同步完成”。
type Once struct {
// done 表示动作是否已经执行。
// 它放在结构体的第一个位置,因为它在热路径中使用。
// 热路径会在每个调用站点内联。
// 将 done 放在第一位可以在某些架构(amd64/386)上产生更紧凑的指令,
// 在其他架构上减少指令(计算偏移量)的数量。
done uint32
m Mutex
}
// Do 在仅在对该 Once 实例的首次调用 Do 时才调用函数 f。
// 换句话说,给定
//
// var once Once
//
// 如果多次调用 once.Do(f),只有第一次调用会调用 f,
// 即使每次调用 f 都有不同的值。每个要执行的函数都需要一个新的 Once 实例。
//
// Do 用于确保只执行一次的初始化。
// 由于 f 是无参数的,可能需要使用函数字面量来捕获要由 Do 调用的函数的参数:
//
// config.once.Do(func() { config.init(filename) })
//
// 由于 Do 不返回,如果 f 导致 Do 被调用,它将导致死锁。
//
// 如果 f 引发 panic,Do 将视为已返回;Do 的未来调用将返回而不调用 f。
func (o *Once) Do(f func()) {
// 注意:这是 Do 的一个错误实现:
//
// if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
// f()
// }
//
// Do 保证返回时 f 已完成。
// 这个实现不会实现这个保证:
// 给定两个同时调用的情况,cas 的获胜者会调用 f,
// 而第二个将立即返回,而不等待第一个调用的 f 完成。
// 这就是为什么慢路径会退回到互斥锁,以及为什么 atomic.StoreUint32 必须推迟到 f 返回之后。
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()
}
}
本文作者:yowayimono
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!