首页  >  文章  >  后端开发  >  为什么“sync.Once”使用“atomic.StoreUint32”而不是“done”标志的常规分配?

为什么“sync.Once”使用“atomic.StoreUint32”而不是“done”标志的常规分配?

Barbara Streisand
Barbara Streisand原创
2024-10-31 05:40:01677浏览

Why does `sync.Once` use `atomic.StoreUint32` instead of a regular assignment for the `done` flag?

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中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn