编辑
2023-12-10
后端
00
请注意,本文编写于 517 天前,最后修改于 517 天前,其中某些信息可能已经过时。

实现

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 许可协议。转载请注明出处!