AtomicStoreUint32 与 Sync.Once 中的赋值
在探索 Go 的sync.Once 类型的源代码时,出现了一个关于用法的问题atomic.StoreUint32 与设置完成标志的常规分配。
不正确的实现:
原始源代码包含不正确的实现:
<code class="go">func (o *Once) Do(f func()) { if atomic.CompareAndSwapUint32(&o.done, 0, 1) { f() } }</code>
此实现无法保证函数返回时 f 已完成。同时调用可能会导致获胜者执行 f,而第二个调用者立即返回(假设第一个调用已完成),但情况可能并非如此。
正确实现:
为了纠正这个问题,当前的实现将atomic.StoreUint32与互斥体结合使用:
<code class="go">func (o *Once) Do(f func()) { if atomic.LoadUint32(&o.done) == 0 { o.doSlow(f) } }</code>
<code class="go">func (o *Once) doSlow(f func()) { o.m.Lock() defer o.m.Unlock() if o.done == 0 { defer atomic.StoreUint32(&o.done, 1) f() } }</code>
为什么使用AtomicStoreUint32?
原子的使用.StoreUint32 对于确保 f 完成后其他 goroutine 可以观察到 o.done 的变化是必要的。虽然原始分配在某些架构上可能是原子的,但 Go 的内存模型需要使用原子包来保证所有支持的架构中的原子操作。
访问完成标志:
目标是确保在互斥体之外对完成标志的访问是安全的。因此,使用原子操作而不是使用互斥锁进行锁定。这种优化提高了快速路径的效率,使sync.Once能够部署在高流量的场景中。
doSlow的互斥体:
doSlow中的互斥体确保在 o.done 设置之前只有一个调用者执行 f。 atomic.StoreUint32 用于写入标志,因为它可能与互斥量临界区之外的atomic.LoadUint32同时发生。
并发写入与读取:
直接读取由于互斥保护,doSlow 中的 o.done 是安全的。此外,同时读取 o.done 和atomic.LoadUint32 是安全的,因为这两个操作都只涉及读取。
以上是为什么“sync.Once”使用“atomic.StoreUint32”而不是“done”标志的常规分配?的详细内容。更多信息请关注PHP中文网其他相关文章!