atomic.AddInt64不能直接当计数器变量用,因其是原子操作函数而非变量;必须配合int64类型共享变量,且所有读写均需通过atomic系列函数,否则引发数据竞争。

atomic.AddInt64 为什么不能直接当“计数器变量”用
它本身不是变量,而是一个原子操作函数——你不能写 var counter = atomic.AddInt64(...) 然后反复赋值;必须配合一个 int64 类型的全局或共享变量,且该变量只能通过 atomic 系列函数读写,否则数据竞争无法避免。
常见错误是混用:比如用 counter++ 或 counter += 1 直接修改,这会绕过原子性,Go 的 -race 检测器立刻报错:fatal error: concurrent map writes(如果该计数器还被用于控制 map 访问)或更泛化的 Data race 提示。
- 必须声明为
var counter int64(不能是int或int32) - 所有写操作统一走
atomic.AddInt64(&counter, 1)、atomic.AddInt64(&counter, -1) - 所有读操作必须用
atomic.LoadInt64(&counter),不能直接读counter - 初始化可直接赋值(如
counter = 0),但首次赋值后就别再裸写
如何安全地在 goroutine 中递增并获取当前值
典型场景是并发任务完成计数,比如启动 10 个 goroutine 处理任务,主 goroutine 等待全部完成。这时不能用普通变量加 sync.Mutex,而应优先用 atomic——更轻量、无锁、性能高。
注意:atomic.AddInt64 返回的是**操作后的值**,所以你可以链式判断:
if atomic.AddInt64(&counter, 1) == int64(totalTasks) {
close(doneCh)
}
但要小心溢出:如果 counter 接近 math.MaxInt64,再加会回绕成负数。生产环境若计数可能超 9×10¹⁸,得加溢出检查或换用 uint64 + atomic.AddUint64(不过 Go 原生不提供 LoadUint64 的原子读,需用 atomic.LoadUint64 ——它存在,只是文档藏得深)。
- 递增并取新值:
newVal := atomic.AddInt64(&counter, 1) - 仅递增不关心结果:
atomic.AddInt64(&counter, 1) - 想实现“先读后加”逻辑?不行——
atomic不提供 fetch-and-add 的返回旧值版本;如有此需,得用atomic.CompareAndSwapInt64手动重试
和 sync.WaitGroup 比,什么情况下该选 atomic.AddInt64
sync.WaitGroup 是为“等待一组 goroutine 结束”设计的,语义明确、易用;atomic.AddInt64 是底层原语,适合需要细粒度控制、或计数需参与其他逻辑(比如限流阈值判断、状态机跳转)的场景。
例如:你维护一个服务请求计数器,每来一个请求就 +1,同时要实时检查是否达到每秒 1000 请求的熔断线——这种“读+条件判断+可能写”的组合,用 WaitGroup 做不了,必须用 atomic 配合 LoadInt64。
- 只等结束?用
sync.WaitGroup,代码更清晰,不易错 - 要读计数值做判断、或与其他原子变量联动?选
atomic.AddInt64+atomic.LoadInt64 - 要减法且可能为负?
atomic.AddInt64(&counter, -1)完全合法,WaitGroup的Done()却要求计数器 > 0 否则 panic
容易被忽略的内存序和编译器重排问题
atomic.AddInt64 默认提供的是 seqcst(sequential consistency)内存序,足够安全,但比 relaxed 开销略大。多数业务场景无需调整,但如果你在高性能网络代理或 ring buffer 实现中压榨最后一点性能,要注意:
Go 的 atomic 包目前**不暴露内存序参数**(不像 C/C++ 的 __atomic_add_fetch),所以你无法指定 acquire 或 release。这意味着:不能靠它来替代 sync/atomic 之外的手动内存屏障,也不能用它同步非原子变量的读写。
- 不要指望
atomic.AddInt64能让上面一行对普通变量的写“对其他 goroutine 可见”——它只保证自己操作的原子性和顺序,不带 fence 语义给周边代码 - 如果需要发布-订阅模式(比如先写数据,再原子更新标志位),仍需搭配
sync/atomic的Store/Load或用chan/mutex - 调试时发现计数“卡住”或“跳变”?先确认有没有 goroutine panic 后没调用递减,再检查是否误把
&counter写成counter导致传值而非传地址


















