为何Go语言要支持atomic.Load与atomic.Store原子操作?
你觉得atomic.Load(addr)和*addr、atomic.Store(addr, newval)和*addr = newval功能等价,但直接读写并非原子操作,核心原因有这几点:
CPU指令拆分风险
不是所有直接读写都会被编译成单条CPU指令。比如32位CPU上操作64位变量,编译器会把读写拆成多条指令执行;就算变量宽度匹配CPU原子操作宽度,编译器或CPU的优化也可能把单逻辑操作拆分成多个步骤,没法保证原子性。编译器优化破坏一致性
Go编译器会做寄存器缓存优化,比如多次读取*addr时,可能只从内存读一次,之后都用寄存器里的旧值,导致看不到其他goroutine的修改。而atomic.Load会强制编译器跳过这类优化,直接从内存读取最新值。内存可见性无保障
就算直接读写是单条指令,并发场景下也没法保证内存可见性。其他goroutine的修改可能因为CPU缓存一致性问题,无法及时被当前goroutine感知。atomic操作会触发内存屏障,强制同步CPU缓存,确保所有goroutine看到的变量值一致。并发竞争导致未定义行为
多goroutine同时读写同一变量时,直接的*addr读写会引发数据竞争,可能读到半更新的值,或者写入被覆盖,程序行为完全不可预测。atomic操作则专门处理这类场景,保证同一时间只有一个goroutine完成读写。
举个实际例子:32位系统上操作int64变量,直接*addr = 0x123456789abcdef会拆成两次32位写入,此时若有其他goroutine读取,大概率会拿到只写了一半的错误值;而atomic.StoreInt64能保证整个64位值的写入是原子完成的,不会出现中间状态。
内容的提问来源于stack exchange,提问作者Markity

