atomic.addint64可安全并发计数,但混用普通读写或类型/对齐不合规会导致数据错误;counter++非原子,等价于读-加-写三步,引发竞态;变量须为int64、包级或堆分配、读写均用atomic函数,结构体需8字节对齐;条件更新需cas循环;多字段、复合类型或需阻塞时应改用sync.mutex。

直接用 atomic.AddInt64 就能安全计数,但只要混用普通读写或类型/对齐不合规,结果必然错——这不是性能问题,是数据正确性彻底失效。
为什么 counter++ 在并发里一定不行
它被编译成“读内存 → 加1 → 写回内存”三步,中间任意时刻都可能被抢占。两个 goroutine 同时读到 5,各自加1后都写回 6,最终只 +1 而非 +2。Go 的 -race 会立刻报 Data Race,不加检测也可能跑出 987 而不是 1000。
- 不是偶发 bug,是硬件级竞态的必然表现
-
counter++和counter += 1都等价于非原子三步操作 - 哪怕只有一处用了普通赋值(如
counter = 0),整个变量就失去原子性保障
atomic.AddInt64 安全使用的硬性条件
函数本身没问题,出错几乎全是变量定义或访问方式不对:
- 变量必须声明为
int64类型(int在 32 位系统上长度不固定,atomic不保证其原子性) - 必须是包级变量或堆分配变量(
var counter int64),不能用短变量声明(counter := int64(0)),后者地址不可靠 - 所有读取必须走
atomic.LoadInt64(&counter),直接读counter可能读到撕裂值(尤其在 32 位系统读 64 位值) - 结构体中若含
int64字段,需确保 8 字节对齐:大类型放前面,例如type T { B int64; A byte },而非{ A byte; B int64 }
带条件的计数必须手动写 CAS 循环
atomic.AddInt64 只负责无条件加法。一旦需要“小于阈值才加”这类逻辑,就得用 atomic.CompareAndSwapInt64 手动实现乐观重试:
func incIfLessThan100() {
for {
old := atomic.LoadInt64(&counter)
if old >= 100 {
return
}
if atomic.CompareAndSwapInt64(&counter, old, old+1) {
return
}
// CAS 失败,说明别人已改,继续重试
}
}
- 别指望
atomic提供“原子条件更新”语义——它没有 - 低竞争时效率高;高竞争下可能反复自旋,CPU 占用飙升,此时不如换
sync.Mutex+ 简单 if 判断 - 循环内不要漏掉
atomic.LoadInt64,缓存旧值到局部变量会导致 CAS 总是失败
什么时候必须放弃 atomic 改用 sync.Mutex
atomic 不是锁的替代品,而是限定场景下的特化工具:
- 要同时更新多个字段(如计数器 + 时间戳 + 错误码)
- 操作目标是
float64、map、struct或任意复合类型(atomic不支持) - 需要阻塞等待(如“等计数器达到 100 再继续”),CAS 无法做到
- 业务逻辑含 IO、网络调用或长耗时计算——CAS 循环里做这些会拖垮调度器
最容易被忽略的一点:只要开始用 atomic,这个变量的所有访问——无论读、写、增、重置——都必须统一走 atomic 函数。混用普通操作,内存可见性和顺序保证瞬间归零,结果不可预测。











