atomic.CompareAndSwapInt64必须传*int64指针,结构体字段需8字节对齐,CAS失败须循环重试,读写必须统一用atomic函数,禁止混用普通操作。

atomic.CompareAndSwapInt64 必须传指针,不是值
直接写 atomic.CompareAndSwapInt64(counter, old, new) 会编译失败,报错:cannot use counter (type int64) as type *int64。Go 的 CAS 函数签名强制要求第一个参数是 *int64,必须显式取地址:&counter。
常见错误还包括对结构体字段直接取地址:&s.x 在某些布局下非法(比如字段未对齐或嵌套过深),此时应把需 CAS 的字段提为顶层变量,或用 unsafe.Offsetof 验证偏移量是否合法。
- 正确:声明
var counter int64,调用atomic.CompareAndSwapInt64(&counter, old, new) - 错误:传
counter、int64(123)或&s.Count(未验证对齐) - 结构体中若含
int64字段,确保其地址 8 字节对齐:unsafe.Offsetof(s.Count) % 8 == 0,否则在 ARM32 或开启-gcflags="-d=checkptr"时 panic
CAS 失败不是错误,必须循环重试
atomic.CompareAndSwapInt64 返回 false 是并发常态,不代表 bug,只说明“你基于的旧值已过期”。一次 if 判断就放弃,大概率丢更新——比如计数器少加 1、状态切换被跳过。
标准模式是自旋读-比-换:
立即学习“go语言免费学习笔记(深入)”;
for {
old := atomic.LoadInt64(&counter)
if atomic.CompareAndSwapInt64(&counter, old, old+1) {
break
}
}
高竞争下可加 runtime.Gosched() 避免空转占满 CPU,但多数场景不需要;更关键的是别加 time.Sleep,它会放大延迟且调度不可控。
- 别省略
atomic.LoadInt64直接用缓存的old,中间可能已被其他 goroutine 修改 - 若冲突频繁(如每秒上千次 CAS 失败),说明不适合用 CAS,该换
sync.Mutex或分片计数 - 失败后是否重试,取决于业务逻辑:用户已取消订单,就不该再尝试更新;状态机切换则通常要一直重试直到成功
Load/Store 必须配对,混用普通读写会读到旧值
用 atomic.StoreInt64(&counter, 42) 写入的值,只能靠 atomic.LoadInt64(&counter) 读到最新值。直接写 counter = 42 或读 val := counter 会绕过内存屏障,大概率看到陈旧缓存值——编译器不报错,但结果不可预测。
竞态检测器(go run -race)能抓到这类问题,但不能依赖它上线后兜底。
- 所有对同一变量的读写,必须统一走 atomic 函数,禁止混用普通操作
- 适用于单次无依赖读取场景:暴露 Prometheus 指标、读开关状态
isRunning、获取序列号当前值 - 不适用于“读完
counter就去查cache[uid]”这类有依赖的逻辑——atomic 不保证这两步之间的顺序
指针 CAS(CompareAndSwapPointer)类型必须严格一致
atomic.CompareAndSwapPointer 是按位比较,哪怕地址差 1 字节也返回 false。它不检查类型,只比内存内容,所以 old 必须来自同一地址的最新快照,且转换必须用 unsafe.Pointer 显式完成。
典型安全用法:
var head unsafe.Pointer
// 初始化
atomic.StorePointer(&head, unsafe.Pointer(&node))
// 更新
for {
old := atomic.LoadPointer(&head)
if atomic.CompareAndSwapPointer(&head, old, unsafe.Pointer(&newNode)) {
break
}
}
- 目标变量必须声明为
unsafe.Pointer类型,不能是*Node或interface{} -
old必须由atomic.LoadPointer获取,不能用局部变量缓存或硬编码nil - 若结构体字段是
*Node,需先取其地址再转:unsafe.Pointer(&p.field),而非unsafe.Pointer(p.field)
atomic.Value 存结构体就能自动同步——其实它返回副本,改了不影响下次 Load。原子操作不是银弹,它只解决单一变量的读写安全,复杂不变式仍得靠锁或更高级同步原语。


















