为什么在sync.Once中,atomic.StoreUint32比普通分配更可取? [英] Why is atomic.StoreUint32 preferred over a normal assignment in sync.Once?

查看:96
本文介绍了为什么在sync.Once中,atomic.StoreUint32比普通分配更可取?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

While reading the source codes of Go, I have a question about the code in 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 ?这些不相等吗?有什么区别?

why is ataomic.StoreUint32 used, rather than, say o.done = 1? Are these not equivalent? What are the differences?

必须使用原子操作( atomic.StoreUint32 )确保其他goroutine可以观察到"f()"的影响.在内存模型较弱的机器上观察到将o.done设置为1之前是什么?

Must we use the atomic operation (atomic.StoreUint32) to make sure that other goroutines can observe the effect of "f()" before o.done is set to 1 is observed on a machine with weak memory model?

推荐答案

请记住,除非您是手工编写程序集,否则您不是在对计算机的内存模型进行编程,而是在对Go的内存模型进行编程.这意味着即使您的体系结构中的原始分配都是原子的,Go仍需要使用原子包来确保所有受支持的体系结构的正确性.

Remember, unless you are writing the assembly by hand, you are not programming to your machine's memory model, you are programming to Go's memory model. This means that even if primitive assignments are atomic with your architecture, Go requires the use of the atomic package to ensure correctness across alls supported architectures.

访问互斥锁之外的 done 标志仅是安全的,没有严格的顺序,因此可以使用原子操作,而不必始终使用互斥锁获得锁.这是一项优化,旨在使快速路径尽可能高效,从而允许在热路径中使用 sync.Once .

Access to the done flag outside of the mutex only needs to be safe, not strictly ordered, so atomic operations can be used instead of always obtaining a lock with a mutex. This is an optimization to make the fast path as efficient as possible, allowing sync.Once to be used in hot paths.

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

The mutex used for doSlow is for mutual exclusion within that function alone, to ensure that only one caller ever makes it to f() before the done flag is set. The flag is written using atomic.StoreUint32, because it may happen concurrently with atomic.LoadUint32 outside of the critical section protected by the mutex.

与写操作(甚至是原子写操作)同时读取 done 字段是一场数据竞赛.仅仅因为该字段是原子读取的,并不意味着您可以使用常规分配来编写该字段,因此首先使用 atomic.LoadUint32 检查该标志,然后使用 atomic.StoreUint32 编写该标志.

Reading the done field concurrently with writes, even atomic writes, is a data race. Just because the field is read atomically, does not mean you can use normal assignment to write it, hence the flag is checked first with atomic.LoadUint32 and written with atomic.StoreUint32

直接在 doSlow 中完成的 done 的读取是安全的,因为互斥锁可以防止它被并发写入.与 atomic.LoadUint32 并发读取值是安全的,因为两者都是读取操作.

The direct read of done within doSlow is safe, because it is protected from concurrent writes by the mutex. Reading the value concurrently with atomic.LoadUint32 is safe because both are read operations.

这篇关于为什么在sync.Once中,atomic.StoreUint32比普通分配更可取?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆