V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
Ainokiseki
V2EX  ›  Go 编程语言

32 位及以上的机器上, atomic.LoadInt32/WriteInt32 是否毫无意义?

  •  
  •   Ainokiseki · 217 天前 · 1707 次点击
    这是一个创建于 217 天前的主题,其中的信息可能已经有所发展或是发生改变。
    我能查到的,原子操作的作用有两点:
    1.保证操作要么执行完成,要么未执行,不会出现中间状态
    2.保证读取的是内存上的最新值,而非缓存在寄存器上的值

    众所周知,32 位及以上的机器对内存的读/写操作的最小单位是 32bit ,也就是说对一个 int32 进行读写,本身即可满足第一条要求。

    至于第二条,可以分两种情况:
    1. 如果不采用其他方如 mutex 来保证读取操作发生在写入操作后,那么读取操作本身就既可能读到新值也可能读到旧值,因此不在意读的是内存还是寄存器
    2. 如果用了 mutex ,那么 mutex 可以保证读操作在写操作后,因此读到的值必然是已经被修改过的值,而非缓存值

    因此,可否认为在 32 位及以上的机器上使用 atomic 包来单纯的读/写 int32 毫无意义,只有 cas 操作由于涉及到一次读和一次写才有用处?
    第 1 条附言  ·  217 天前
    @ysc3839 第一条要求确实要以内存对齐为前提
    @Orlion 文章已拜读,看起来 atomic write 的意义在于使用了写屏障解决了伪重排序的问题。考虑到编译器本身就有可能进行代码重排,因此如果潜在的代码重排(无论是缓存引起的伪重排还是编译器的重排)会导致代码逻辑问题,那么确实应当使用原子写来解决这一问题。

    另外,楼主又有一个新的疑问。回复中有提到编译器可能对代码进行一些优化,那么如果某个变量在当前 goroutine 中没有被修改,但事实上会被另外的 goroutine 修改,编译器会认为这个变量的值是恒定的吗?会因此省略一些读操作吗?
    8 条回复    2024-05-15 18:42:53 +08:00
    lcdtyph
        1
    lcdtyph  
       217 天前 via iPhone   ❤️ 1
    在 x86_64 架构上:
    变量地址对齐的情况下,atomic read/write 确实就是一条 mov 指令
    但是 atomic 包应该还会额外提供插入 mfence 来阻止指令 store-load 重排的问题
    coyove
        2
    coyove  
       217 天前
    请搜 memory order
    ysc3839
        3
    ysc3839  
       217 天前 via Android
    “也就是说对一个 int32 进行读写,本身即可满足第一条要求”
    否的,要考虑对齐情况
    PTLin
        4
    PTLin  
       217 天前
    给你补充下,go 我不太清楚,Linux 内核的原子变量的 api 还有个功能就是防止操作被编译器优化。
    假设一个循环(无内存屏障,同步原语情况下)里有 a = b ,编译器有可能把这句优化到循环之外,因为编译器的角度这句话运行一次和运行多次无异,但是我们知道 b 是被多个执行流共享的变量,所以需要使用 api 防止操作被优化。
    MoYi123
        5
    MoYi123  
       217 天前
    内存可见性, 指令重排都不能保证
    Orlion
        6
    Orlion  
       217 天前   ❤️ 2
    在 x86_64 上 atomic.LoadInt32 实际源码就是直接取数据,对应源码如下:
    ```
    func Load(ptr *uint32) uint32 {
    return *ptr
    }
    ```
    而 WriteInt32 有所不同,其中使用了 XCHGL ,这个指令有 LOCK 指令前缀的效果,可以保证可见性。
    ```
    TEXT ·Store(SB), NOSPLIT, $0-12
    MOVQ ptr+0(FP), BX
    MOVL val+8(FP), AX
    XCHGL AX, 0(BX)
    RET
    ```

    因此在 x86_64 平台上不使用 atomic 包也能保证原子性,但是保证不了可见性,如果程序中对可见性没有要求,以我的认知我觉得可以不使用 atomic 包,话虽如此实际工作中并发场景中该加还是加吧,说不准认知之外还有什么别的坑呢?

    btw ,多年前我对这个问题也有过一段时间疑问与探索,整理了一篇博客: https://blog.fanscore.cn/a/34/ 可以参考一下。
    GeruzoniAnsasu
        7
    GeruzoniAnsasu  
       217 天前
    请参考 c++ 的 memory order: https://zh.cppreference.com/w/cpp/atomic/memory_order

    这堆内存序的说明可以帮助你理解为什么要有专门的原子操作函数
    lance6716
        8
    lance6716  
       217 天前 via Android
    官方文档有说明,go 内存模型符合 drf-sc ,所以写程序符合 data race free 的约定就没啥问题
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3140 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 13:03 · PVG 21:03 · LAX 05:03 · JFK 08:03
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.