atomic函数参数必须传&x而非x,因其底层依赖cpu原子指令(如lock xadd),操作目标必须是内存中固定可寻址位置;传值是副本,地址失效,传指针才锁定确切地址。

为什么 atomic 函数参数必须传 &x 而不是 x
因为 atomic 操作不是“对值做原子处理”,而是“对内存地址上的值做原子读写”——它底层依赖 CPU 的原子指令(如 LOCK XADD),这些指令操作目标必须是内存中一个固定、可寻址的位置。
传值(x)意味着传的是副本,地址随时失效;传指针(&x)才真正告诉 CPU:“请锁定并操作这个确切地址上的数据”。Go 的 atomic 系列函数签名全是 func LoadInt64(ptr *int64) int64 这种形式,不是设计选择,是硬件约束。
-
atomic.AddInt64不是给数字加 1,是“在 ptr 指向的内存位置上执行原子加法” - 如果传局部变量地址(比如函数内
var x int64; atomic.StoreInt64(&x, 1)),函数返回后&x可能已被复用,行为未定义 - 传结构体字段地址(如
&s.counter)通常可行,但需确保该结构体本身生命周期足够长且不被移动(例如不能是栈上临时 struct)
atomic.LoadUint64 总返回 0 是什么情况
这不是函数坏了,而是你传了一个“没初始化”或“地址不稳定”的 *uint64。最常见两种:变量声明了但没赋初值(零值是 0),或者取了栈上临时变量的地址。
- 包级变量安全:
var counter uint64 = 100→atomic.LoadUint64(&counter)✅ - 堆上分配也行:
p := new(uint64); *p = 42; atomic.LoadUint64(p)✅ - 函数内局部变量不行:
func bad() { var x uint64; atomic.LoadUint64(&x) }❌(&x在函数返回后失效) - 更隐蔽的错误:循环里反复声明同名变量,导致每次
&x指向不同地址,甚至触发栈溢出或数据错乱
int64 在 32 位系统上为何必须用 atomic 访问
因为 int64 在 32 位架构上不是“自然对齐的原子单位”——CPU 无法用一条指令读写完整的 64 位,必须拆成两次 32 位操作。此时若并发读写,可能读到“高 32 位是旧值、低 32 位是新值”的撕裂值(torn read)。
立即学习“go语言免费学习笔记(深入)”;
- 直接
counter++或=赋值会 panic(Go 运行时检测到非对齐写入)或静默出错 -
atomic.LoadInt64/StoreInt64在 32 位平台会自动降级为带锁的模拟实现,保证正确性(但性能略低) - 跨平台兼容建议:统一用
int64/uint64,别用裸int(长度不固定)
CAS 失败不是 bug,是并发常态
atomic.CompareAndSwapInt32 返回 false,不代表代码错了,只说明“我尝试把值从 A 改成 B 时,发现它已经被别人改成 C 了”。这是无锁编程的正常反馈,你得自己决定下一步。
- 状态跃迁场景(如启动标志):
for !atomic.CompareAndSwapInt32(&state, 0, 1) { runtime.Gosched() } - 计数器自增不能靠 CAS 单次完成,得用
atomic.AddInt64—— 它是硬件原生支持的单指令原子操作 - 用
atomic.SwapPointer替换配置时,务必确保新旧指针指向的类型完全一致,否则unsafe.Pointer强转会崩溃
最容易被忽略的一点:所有原子变量必须有稳定地址。这不是风格建议,是内存模型硬性要求——地址一变,缓存、屏障、可见性全失效。










