Go 中的原子操作是否确保其他变量对其他线程可见?

yao*_*jun 5 memory-model go memory-barriers

这让我很困惑,我正在阅读 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)
}
Run Code Online (Sandbox Code Playgroud)

互斥锁通过原子解锁

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

Lock: atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) 
Run Code Online (Sandbox Code Playgroud)

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

在java中,我知道AtomicInteger,内存屏障通过“易失性”,保持线程字段可见。

Arm*_*ani 5

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

拉斯·考克斯 (Russ Cox) 评论说,定义行为是一个大问题

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

相关答案/sf/answers/4122465581/

  • 根据最新版本的文档,当原子写入被原子读取看到时,写入在该读取之前同步,因此会引发发生在边缘之前。https://go.dev/ref/mem#atomic (2认同)

小智 1

测试程序:

\n
package main\n\nimport (\n    "sync/atomic"\n)\n\nvar n uint32\n\nfunc main() {\n    n = 100\n    atomic.AddUint32(&n, 1)\n}\n
Run Code Online (Sandbox Code Playgroud)\n

通过以下方式检查装配:

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

锁定指令

\n

导致处理器\xe2\x80\x99s LOCK#信号在执行附带指令期间被置位(将指令转变为原子指令)。在多处理器环境中,LOCK# 信号可确保在该信号置位时处理器独占使用任何共享内存。\n在大多数 IA-32 和所有 Intel 64 处理器中,在未置位 LOCK# 信号的情况下也可能发生锁定。有关详细信息,请参阅下面的 \xe2\x80\x9cIA-32 体系结构兼容性\xe2\x80\x9d 部分。\nLOCK 前缀只能添加到以下指令的前面,并且只能添加到目标操作数为存储器操作数: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# 信号。\nLOCK 前缀通常与 BTS 指令一起使用,以在共享内存环境中的内存位置上执行读取-修改-写入操作。\ nLOCK前缀的完整性不受内存字段对齐的影响。对于任意未对齐的字段,会观察到内存锁定。\n此指令\xe2\x80\x99s 操作在非 64 位模式和 64 位模式下是相同的。

\n

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

\n