【问题标题】:Do atomic operations in Go make sure other variables are visible to other threads?Go 中的原子操作是否确保其他变量对其他线程可见?
【发布时间】:2021-03-23 08:51:48
【问题描述】:

这让我很困惑,我在阅读 golang 内存模型,https://golang.org/ref/mem

var l sync.Mutex
var a string

func f() {
    a = "hello, world"
    l.Unlock()
}

func main() {
    l.Lock()
    go f()
    l.Lock()
    print(a)
}

原子互斥锁解锁

UnLock: new := atomic.AddInt32(&m.state, -mutexLocked)

Lock: atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) 

我的问题是,如果原子 AddInt32,CompareAndSwapInt32 会导致内存障碍,如果 a 将在不同的 goroutine 中可见。

在java中,我知道AtomicInteger,“volatile”的内存屏障,保持线程字段可见。

【问题讨论】:

    标签: go memory-barriers memory-model


    【解决方案1】:

    Go 没有 volatile 等价物。原子内存模型在 Go 中没有很好地定义,所以为了超级安全,你不应该假设任何东西,即对 a 的更改可能是不可见的。但在实践中,据我了解,所有架构都会设置内存栅栏,因此您是安全的。

    big issue 定义行为,comment from Russ Cox

    是的,我在去年冬天花了一段时间,但还没有机会把它写好。简短的版本是我相当确定规则将是 Go 的原子保证原子变量之间的顺序一致性(行为类似于 C/C++ 的 seqconst 原子),并且您不应该混合给定的原子和非原子访问记忆词。

    相关回答https://stackoverflow.com/a/58892365/2133484

    【讨论】:

    • 那句话并没有真正解决这个问题的含义。这里的问题是原子 RMW 是否具有获取和/或释放语义,以确保所有非原子操作对另一个线程可见,该线程的获取操作与生产者中的释放操作同步。在 asm 中,在某些 ISA 上可以执行弱排序的原子 RMW,但如果没有像 C++ 的 std::memory_order 可选参数这样的机制来指定松弛与 acq_rel 或 seq_cst,那将不是一个好的设计。
    • 但希望“原子变量之间的顺序一致性”也意味着非原子访问的顺序。那些 seq_cst 操作,就像在 C++ 中一样。
    【解决方案2】:

    测试程序:

    package main
    
    import (
        "sync/atomic"
    )
    
    var n uint32
    
    func main() {
        n = 100
        atomic.AddUint32(&n, 1)
    }
    

    通过以下方式检查程序集:

    go tool compile -S main.go         
    "".main STEXT nosplit size=27 args=0x0 locals=0x0 funcid=0x0
        0x0000 00000 (main.go:9)    TEXT    "".main(SB), NOSPLIT|ABIInternal, $0-0
        0x0000 00000 (main.go:9)    FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (main.go:9)    FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (main.go:10)   MOVL    $100, "".n(SB)
        0x000a 00010 (main.go:11)   MOVL    $1, AX
        0x000f 00015 (main.go:11)   LEAQ    "".n(SB), CX
        0x0016 00022 (main.go:11)   LOCK
        0x0017 00023 (main.go:11)   XADDL   AX, (CX)
        0x001a 00026 (main.go:12)   RET
        0x0000 c7 05 00 00 00 00 64 00 00 00 b8 01 00 00 00 48  ......d........H
        0x0010 8d 0d 00 00 00 00 f0 0f c1 01 c3                 ...........
        rel 2+4 t=15 "".n+-4
        rel 18+4 t=15 "".n+0
    go.cuinfo.packagename. SDWARFCUINFO dupok size=0
        0x0000 6d 61 69 6e                                      main
    ""..inittask SNOPTRDATA size=24
        0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0010 00 00 00 00 00 00 00 00                          ........
    "".n SNOPTRBSS size=4
    type..importpath.sync/atomic. SRODATA dupok size=13
        0x0000 00 0b 73 79 6e 63 2f 61 74 6f 6d 69 63           ..sync/atomic
    gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8
        0x0000 01 00 00 00 00 00 00 00                 
    

    LOCK指令is

    导致处理器的 LOCK# 信号在伴随指令的执行期间被断言(将指令转换为原子指令)。在多处理器环境中,LOCK# 信号确保处理器在信号被断言时独占使用任何共享内存。 在大多数 IA-32 和所有 Intel 64 处理器中,锁定可能会发生而没有 LOCK# 信号被断言。有关详细信息,请参阅下面的“IA-32 架构兼容性”部分。 LOCK 前缀只能添加到以下指令,并且只能添加到目标操作数是内存操作数的那些指令形式:ADD、ADC、AND、BTC、BTR、BTS、CMPXCHG、CMPXCH8B、CMPXCHG16B、DEC、INC、 NEG、NOT、OR、SBB、SUB、XOR、XADD 和 XCHG。如果 LOCK 前缀与这些指令之一一起使用并且源操作数是内存操作数,则可能会生成未定义的操作码异常 (#UD)。如果 LOCK 前缀与任何不在上述列表中的指令一起使用,也会产生未定义的操作码异常。无论是否存在 LOCK 前缀,XCHG 指令始终断言 LOCK# 信号。 LOCK 前缀通常与 BTS 指令一起使用,以对共享内存环境中的内存位置执行读-修改-写操作。 LOCK 前缀的完整性不受内存字段对齐的影响。对于任意未对齐的字段,会观察到内存锁定。 该指令在非 64 位模式和 64 位模式下的操作是一样的。

    所以是的,它具有内存可见性。

    【讨论】:

    • 问题是询问 other 变量的可见性,例如使用原子释放/获取线程之间的同步是否可以安全地读取a = "hello, world" 非原子分配的结果。
    • 哦...它在问这个问题。 LOCK 会产生“发生在之前”的效果吗?身份证。
    • 在 x86 上,是的,它也是一个完整的内存屏障,所以 lock inc 等同于 C++ std::atomic<T> .fetch_add 和默认的 memory_order_seq_cst。 (由于 TSO 内存模型,x86 加载具有“获取”语义。)当然,使用 x86 的强内存模型,即使是普通的 mov 存储和普通的 mov 加载也会创建一个发布/获取“同步”,给你线程间“发生在之前”。只要编译器不在编译时重新排序。 how are barriers/fences and acquire, release semantics implemented microarchitecturally?
    猜你喜欢
    • 2018-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-16
    • 2023-03-10
    • 2016-12-28
    • 1970-01-01
    相关资源
    最近更新 更多