Go 为什么atomic.StoreUint32优先于同步的普通赋值。一次?

Go 为什么atomic.StoreUint32优先于同步的普通赋值。一次?,go,Go,在阅读Go的源代码时,我对src/sync/once.Go中的代码有一个问题: func (o *Once) Do(f func()) { // Note: Here is an incorrect implementation of Do: // // if atomic.CompareAndSwapUint32(&o.done, 0, 1) { // f() // } // // Do guarantees tha

在阅读Go的源代码时,我对src/sync/once.Go中的代码有一个问题:

func (o *Once) Do(f func()) {
    // Note: Here is an incorrect implementation of Do:
    //
    //  if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
    //      f()
    //  }
    //
    // Do guarantees that when it returns, f has finished.
    // This implementation would not implement that guarantee:
    // given two simultaneous calls, the winner of the cas would
    // call f, and the second would return immediately, without
    // waiting for the first's call to f to complete.
    // This is why the slow path falls back to a mutex, and why
    // the atomic.StoreUint32 must be delayed until after f returns.

    if atomic.LoadUint32(&o.done) == 0 {
        // Outlined slow-path to allow inlining of the fast-path.
        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()
    }
}
为什么要使用
ataomic.StoreUint32
,而不是说
o.done=1
?这些不是等价的吗?有什么区别


我们必须使用原子操作(
atomic.StoreUint32
)来确保在具有弱内存模型的机器上观察到o.done设置为1之前,其他goroutine可以观察到“f()”的效果吗?

记住,除非您手动编写程序集,否则您不会对机器的内存模型进行编程,您正在编程to Go的内存模型。这意味着,即使原语分配在您的体系结构中是原子的,Go也需要使用原子包来确保跨所有支持的体系结构的正确性

对互斥体外部的
done
标志的访问只需要安全,而不需要严格排序,因此可以使用原子操作,而不是始终使用互斥体获取锁。这是一种优化,使快速路径尽可能高效,允许在热路径中使用
sync.Once

用于
doSlow
的互斥锁仅用于该函数中的互斥,以确保在设置
done
标志之前,只有一个调用者进入
f()
。该标志是使用
atomic.StoreUint32
编写的,因为它可能与受互斥保护的关键部分之外的
atomic.LoadUint32
同时发生

与写入(甚至原子写入)同时读取
done
字段是一种数据竞争。仅仅因为字段是以原子方式读取的,并不意味着您可以使用普通赋值来写入它,因此首先使用
atomic.LoadUint32
检查标志,然后使用
atomic.StoreUint32
写入标志


doSlow
中直接读取
done
是安全的,因为互斥锁可以防止并发写入。与原子加载单元32同时读取值是安全的,因为两者都是读取操作。

我的直觉是,非原子写入不一定对加载单元32可见。虽然写操作是在锁下完成的,但读操作不是。go memory模型中有一个公开的bug,详细描述了类似的案例,因此很难确定这是否正确。我不确定这为什么会遭到否决。这个问题非常清楚,这是一个关于go记忆模型的合理问题。当然,大多数人不必在意这样的细节,但也许金华有理由在意。@kingwah001这样想是错误的。根据语言规范,好的代码是正确的,而不是它是否在特定的机器上工作。因为在原则上(虽然这不是典型的“走的方式”),编译器可以进行优化,破坏与语言规范相反的程序。A以前被问过(但没有回答)。@kingwah001:注意,没有强内存模型的机器是存在的。在这里,普通内存读或写可能只使用CPU端缓存,例如:您必须发出一条特殊指令(例如,加载锁定并存储条件指令,或内存屏障或缓存刷新指令),让CPU实际查阅任何共享内存,而其他CPU可能也在读和/或写。例如,PowerPC和SPARC(V9)使用这些类型的操作。