必须用int64类型、取地址传参、所有读写走atomic函数,否则会panic或竞态;int/++非原子且可能未对齐;loadint64仅适用于非同步读,同步需loadacquire;cas失败须退避,多字段更新需锁;变量须在堆或包级生命周期。

直接用 atomic.AddInt64 就行,但必须满足三个硬性条件:变量是 int64 类型、取地址传参(&counter)、所有读写都走 atomic 函数——漏掉任意一条,不是 panic 就是竞态。
为什么不能用 int 或 counter++
Go 的 ++ 不是原子操作,底层拆成「读内存→加 1→写回」三步,两个 goroutine 同时执行就可能丢失一次更新。Go race detector 会报类似 Data race on variable 'counter' 的错误。int 类型在 32 位系统或某些 GC 场景下可能非对齐,传给 atomic.AddInt64 会直接 panic:sync/atomic: unaligned 64-bit operand。
- 声明计数器必须写成
var counter int64,不能是int、int32或未指定类型的var counter = 0 - 递增只能用
atomic.AddInt64(&counter, 1),禁用counter++、counter += 1、atomic.AddInt(该函数不存在) - 初始化可依赖零值(
var counter int64默认为 0),但显式写var counter int64 = 0更稳妥
atomic.LoadInt64 读取时容易忽略的内存顺序问题
单纯读取计数器用于打点、暴露 Prometheus 指标时,atomic.LoadInt64(&counter) 足够;但如果读操作用于同步逻辑(比如等待计数器达到阈值才继续),就不能只靠它。
-
atomic.LoadInt64是 relaxed load,不保证之前其他内存操作的完成顺序 - 若你在写入
counter前还更新了另一个标志位done,又希望读counter时一定看到最新的done,就得用atomic.LoadAcquire(Go 1.19+) - 绝对不要写
for atomic.LoadInt64(&done) == 0 { }这类忙等——编译器可能优化掉重复读,且 CPU 白耗;应改用sync.WaitGroup或chan struct{}
需要条件更新(如限流)时,atomic.AddInt64 就不够用了
像「如果当前请求数 atomic.LoadInt64 + if + atomic.AddInt64 实现——两步之间存在竞态窗口,多个 goroutine 可能同时通过判断并执行加法,导致超限。
- 必须用
atomic.CompareAndSwapInt64写 CAS 循环,例如:for {<br> old := atomic.LoadInt64(&counter)<br> if old >= 1000 {<br> return false<br> }<br> if atomic.CompareAndSwapInt64(&counter, old, old+1) {<br> return true<br> }<br>} - CAS 失败要主动退出或退避,避免死循环榨干 CPU;高频失败场景建议直接切到
sync.Mutex -
atomic不支持多字段事务,比如同时更新reqCount和lastResetTime,必须用锁
最易被忽略的是变量生命周期:别对局部变量取地址传给 atomic 函数,栈上变量回收后地址失效;计数器应定义在包级、struct 字段里,或显式 new(int64) 分配在堆上。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











