首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >atomic.Load和atomic.Store的意义是什么?

atomic.Load和atomic.Store的意义是什么?
EN

Stack Overflow用户
提问于 2019-10-27 17:43:31
回答 1查看 436关注 0票数 4

在Go的内存模型中,没有任何关于原子及其与内存围栏的关系的说明。

尽管许多内部包似乎依赖于内存排序,但如果atomics在其周围创建内存围栏,则可以提供内存排序。详情请参见本期

在不了解它的真正工作原理之后,我访问了源代码,特别是amd64.go,并发现了LoadStore的以下实现:

代码语言:javascript
复制
//go:nosplit
//go:noinline
func Load(ptr *uint32) uint32 {
    return *ptr
}

Store是在同一个包中用asm_amd64.s实现的。

代码语言:javascript
复制
TEXT runtime∕internal∕atomic·Store(SB), NOSPLIT, $0-12
    MOVQ    ptr+0(FP), BX
    MOVL    val+8(FP), AX
    XCHGL   AX, 0(BX)
    RET

两人似乎都与并行无关。

我确实研究过其他架构,但实现似乎是等价的。

但是,如果atomics确实很弱并且没有提供内存排序的保证,那么下面的代码可能会失败,但它不会。

另外,我尝试用简单的赋值替换原子调用,但在这两种情况下,它仍然产生一致和“成功”的结果。

代码语言:javascript
复制
func try() {
    var a, b int32

    go func() {
        // atomic.StoreInt32(&a, 1)
        // atomic.StoreInt32(&b, 1)
        a = 1
        b = 1
    }()

    for {
        // if n := atomic.LoadInt32(&b); n == 1 {
        if n := b; n == 1 {
            if a != 1 {
                panic("fail")
            }
            break
        }
        runtime.Gosched()
    }
}

func main() {
    n := 1000000000
    for i := 0; i < n ; i++ {
        try()
    }
}

下一个想法是,编译器会做一些神奇的工作来提供排序保证。因此,下面是带有原子StoreLoad未注释的变量的清单。完整的清单可以在巴斯丁上找到。

代码语言:javascript
复制
// Anonymous function implementation with atomic calls inlined

TEXT %22%22.try.func1(SB) gofile../path/atomic.go
        atomic.StoreInt32(&a, 1)
  0x816         b801000000      MOVL $0x1, AX
  0x81b         488b4c2408      MOVQ 0x8(SP), CX
  0x820         8701            XCHGL AX, 0(CX)
        atomic.StoreInt32(&b, 1)
  0x822         b801000000      MOVL $0x1, AX
  0x827         488b4c2410      MOVQ 0x10(SP), CX
  0x82c         8701            XCHGL AX, 0(CX)
    }()
  0x82e         c3          RET
代码语言:javascript
复制
// Important "cycle" part of try() function

 0x6ca          e800000000      CALL 0x6cf      [1:5]R_CALL:runtime.newproc
    for {
  0x6cf         eb12            JMP 0x6e3
        runtime.Gosched()
  0x6d1         90          NOPL
    checkTimeouts()
  0x6d2         90          NOPL
    mcall(gosched_m)
  0x6d3         488d0500000000      LEAQ 0(IP), AX      [3:7]R_PCREL:runtime.gosched_m·f
  0x6da         48890424        MOVQ AX, 0(SP)
  0x6de         e800000000      CALL 0x6e3      [1:5]R_CALL:runtime.mcall
        if n := atomic.LoadInt32(&b); n == 1 {
  0x6e3         488b442420      MOVQ 0x20(SP), AX
  0x6e8         8b08            MOVL 0(AX), CX
  0x6ea         83f901          CMPL $0x1, CX
  0x6ed         75e2            JNE 0x6d1
            if a != 1 {
  0x6ef         488b442428      MOVQ 0x28(SP), AX
  0x6f4         833801          CMPL $0x1, 0(AX)
  0x6f7         750a            JNE 0x703
  0x6f9         488b6c2430      MOVQ 0x30(SP), BP
  0x6fe         4883c438        ADDQ $0x38, SP
  0x702         c3          RET

正如你所看到的,没有篱笆或锁再次到位。

注意:所有测试都是在x86_64和i5-8259 u上进行的。

问题:

那么,在函数调用中包装简单指针取消引用有什么意义吗?或者它是否有隐藏的意义?为什么这些atomics仍然作为内存屏障工作?(如果是的话)

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-10-27 18:34:00

我一点也不知道Go,但是看起来.load() .store() 的x86-64实现可能是故意的/有原因的!

加载的//go:noinline意味着编译器不能围绕一个非内联的黑匣子重新排序,我想。在x86上,这就是顺序一致性( acq-rel )加载端所需的全部内容。普通的x86 mov负载是获取负载。

编译器生成的代码可以利用x86的强有序内存模型,该模型是顺序一致性+存储缓冲区(具有存储转发),即acq/rel.为了恢复顺序一致性,只需要在发布存储后耗尽存储缓冲区。

.store()是用asm编写的,加载它的堆栈args,并使用xchg作为seq存储。

带内存的XCHG有一个隐式lock前缀,这是一个完全障碍;它是mov+mfence的一个有效替代方案,可以实现C++称为memory_order_seq_cst存储的内容。

它在以后加载之前刷新存储缓冲区,并且允许存储区访问L1d缓存。为什么具有顺序一致性的std::原子存储使用XCHG?

看见

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58581820

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档