atomic.AddInt64不可用counter++替代,因后者是非原子的读-改-写操作,会导致数据竞争和丢失更新;必须统一使用atomic包的Load/Store/CAS系列函数,且类型、对齐、生命周期须严格符合要求。

atomic.AddInt64 为什么不能直接用 counter++ 替代
因为 counter++ 是非原子的“读-改-写”三步操作,多个 goroutine 并发执行时会相互覆盖——比如两个 goroutine 同时读到 counter == 5,各自加 1 后都写回 6,最终只 +1 而非 +2。Go 编译器和 CPU 都不保证其原子性,go run -race 必报 data race。
-
atomic.AddInt64(&counter, 1)由 CPU 指令(如 x86 的LOCK XADD)保证单条指令完成,无中间态 - 必须传地址:
&counter,且counter类型必须是int64(不能是int,平台依赖) - 所有读写都得走 atomic:一旦用了
atomic.AddInt64,就不能再用counter = 10或fmt.Println(counter)直接访问,否则破坏内存可见性
Load/Store 是唯一安全读写方式,别漏掉 Load
很多人只记得用 atomic.AddInt64 增量,却在最后用 fmt.Println(counter) 直接读——这会导致读到陈旧值或未同步的缓存副本。Go 的 memory model 要求:只要用了 atomic 写,就必须用 atomic.LoadInt64 读,才能确保看到最新结果。
-
atomic.StoreInt64(&x, 100):写入立即对其他 goroutine 可见(带 full memory barrier) -
atomic.LoadInt64(&x):强制从主内存/最新缓存加载,不读寄存器残留 - 不要混用:比如用
atomic.StoreUint32写,却用atomic.LoadUint64读——类型不匹配会 panic 或返回垃圾值
CompareAndSwapInt32 实现“只执行一次”的底层逻辑
sync.Once 就是靠 atomic.CompareAndSwapUint32 实现的。它不是锁,而是乐观判断:“如果当前值还是我预期的,那就换成新值;否则说明别人已经干过了,我退出”。适合初始化、状态跃迁等一次性操作。
- 典型模式:
atomic.CompareAndSwapInt32(&state, 0, 1),返回true表示抢到了,false表示已被抢占 - 别指望它自动重试:失败就返回
false,要不要循环、要不要让出调度(runtime.Gosched()),得你自己写 - 32 位更稳妥:在 32 位系统上,
int64的 CAS 需要额外指令支持,性能略低,简单开关推荐用int32
结构体字段、局部变量、int/uint 是三大高危误用点
这些地方看似能编译通过,但运行时可能 panic、数据错乱或行为未定义——尤其 Go 1.19+ 在非对齐地址上会直接 crash。
立即学习“go语言免费学习笔记(深入)”;
- 结构体字段:如
type S struct { a byte; x int64 }中的s.x地址很可能不对齐,禁止对其调用atomic.LoadInt64(&s.x) - 局部变量取地址:函数内
var x int64; p := &x,函数返回后p指向栈内存,已失效 -
int和uint:长度平台相关(32/64 位),atomic不支持;必须显式用int32、int64、uint64等固定宽度类型
最省心的做法:声明为包级变量(如 var counter int64),所有操作统一用 atomic.AddInt64(&counter, ...) 和 atomic.LoadInt64(&counter) ——对齐、生命周期、类型都稳了。










