能,但仅限于纯整数累加/减等无条件原子操作;涉及读-改-写、多字段联动或条件判断时必须用sync.Mutex或CAS循环。

atomic.AddInt64 能否安全替代 mutex 做计数?
能,但仅限于「纯整数累加/减」这类无条件、无依赖的原子操作。它比 sync.Mutex 开销小得多,且不会阻塞 goroutine,适合高频、低逻辑复杂度的计数场景(比如请求量统计、错误计数器)。但一旦需要“读-改-写”组合逻辑(例如“如果值小于 100 才加 1”),就不能只靠 atomic.AddInt64,得配合 atomic.CompareAndSwapInt64 或退回到锁。
为什么直接用 int64 变量 + 普通 += 会出错?
因为 += 不是原子操作:它实际分三步——读内存、CPU 计算、写回内存。多个 goroutine 同时执行时,可能都读到旧值,各自加 1 后写回,导致最终结果比预期少。这种竞态无法通过编译器或运行时自动发现,只能靠 go run -race 检测或逻辑推演。
常见错误现象:
- 计数器增长明显慢于并发请求数
- 多次运行结果不一致
- go run -race main.go 报出 Read at ... by goroutine X / Previous write at ... by goroutine Y
正确做法:
- 声明变量时用
int64类型(int在 32 位系统上非原子) - 初始化必须用
var counter int64或counter := int64(0),不能省略类型 - 所有读写都走
atomic包函数:加用atomic.AddInt64(&counter, 1),读用atomic.LoadInt64(&counter)
atomic.AddInt64 的参数和返回值要注意什么?
atomic.AddInt64(ptr *int64, delta int64) 第一个参数是指针,不是值;第二个参数可正可负(支持减法)。它**返回操作后的最新值**,不是旧值 —— 这点常被误用。
使用场景示例:
var hits int64
go func() {
for i := 0; i < 1000; i++ {
// 返回的是加完后的值,可用于判断阈值
if atomic.AddInt64(&hits, 1) > 500 {
log.Println("hit limit")
}
}
}()容易踩的坑:
- 传值而非地址:写成
atomic.AddInt64(counter, 1)→ 编译报错:cannot use counter (type int64) as type *int64 - 混用普通读取:
fmt.Println(hits)→ 竞态!必须用atomic.LoadInt64(&hits) - 对同一地址同时调用
AddInt64和StoreInt64→ 逻辑混乱,但语法合法;应统一风格
性能差异有多大?什么时候该换回 sync.Mutex?
在现代 CPU 上,atomic.AddInt64 通常只需 10–20 纳秒,而一次 sync.Mutex 的 lock/unlock 至少几百纳秒,且存在调度开销。但 atomic 的优势只在线性累加场景成立。
一旦出现以下情况,就该考虑锁或更高级原语:
- 需要基于当前值做分支逻辑(如“若 count == 0 则重置状态”)
- 要同时更新多个关联变量(如
count和lastUpdated) - 计数需配合 I/O(如每 1000 次写一次日志)→ 此时瓶颈已不在计数本身,锁的代价变得可接受
没有银弹。atomic 是工具箱里最快的一把螺丝刀,但拧不动需要扭矩控制的螺栓。


















