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

217 天前
 Ainokiseki
我能查到的,原子操作的作用有两点:
1.保证操作要么执行完成,要么未执行,不会出现中间状态
2.保证读取的是内存上的最新值,而非缓存在寄存器上的值

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

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

因此,可否认为在 32 位及以上的机器上使用 atomic 包来单纯的读/写 int32 毫无意义,只有 cas 操作由于涉及到一次读和一次写才有用处?
1707 次点击
所在节点    Go 编程语言
8 条回复
lcdtyph
217 天前
在 x86_64 架构上:
变量地址对齐的情况下,atomic read/write 确实就是一条 mov 指令
但是 atomic 包应该还会额外提供插入 mfence 来阻止指令 store-load 重排的问题
coyove
217 天前
请搜 memory order
ysc3839
217 天前
“也就是说对一个 int32 进行读写,本身即可满足第一条要求”
否的,要考虑对齐情况
PTLin
217 天前
给你补充下,go 我不太清楚,Linux 内核的原子变量的 api 还有个功能就是防止操作被编译器优化。
假设一个循环(无内存屏障,同步原语情况下)里有 a = b ,编译器有可能把这句优化到循环之外,因为编译器的角度这句话运行一次和运行多次无异,但是我们知道 b 是被多个执行流共享的变量,所以需要使用 api 防止操作被优化。
MoYi123
217 天前
内存可见性, 指令重排都不能保证
Orlion
217 天前
在 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
217 天前
请参考 c++ 的 memory order: https://zh.cppreference.com/w/cpp/atomic/memory_order

这堆内存序的说明可以帮助你理解为什么要有专门的原子操作函数
lance6716
217 天前
官方文档有说明,go 内存模型符合 drf-sc ,所以写程序符合 data race free 的约定就没啥问题

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1040938

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX