Golang sync/atomic标准库如何使用_Golang原子操作说明

10次阅读

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

Golang sync/atomic标准库如何使用_Golang原子操作说明

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 = 10fmt.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 指向内存,已失效
  • intuint:长度平台相关(32/64 位),atomic 不支持;必须显式用 int32int64uint64 等固定宽度类型

最省心的做法:声明为包级变量(如 var counter int64),所有操作统一用 atomic.AddInt64(&counter, ...)atomic.LoadInt64(&counter) ——对齐、生命周期、类型都稳了。

text=ZqhQzanResources