atomic.addint64 更快因其编译为 lock xadd 指令,全程用户态执行;而 sync.mutex 在竞争时需陷入内核态并触发上下文切换,延迟高十余倍,但仅保证单操作原子性,不支持复合逻辑。

atomic.AddInt64 为什么比 mutex.Lock() 更快
因为 atomic.AddInt64 直接编译成 CPU 的 LOCK XADD 指令,全程在用户态完成,不触发调度器介入;而 sync.Mutex 在竞争激烈时会陷入内核态、挂起 goroutine、触发上下文切换——这在每秒数万次计数的场景下,延迟差异可达 10 倍以上。
但要注意:它只保证单个操作原子,不提供“读-改-写”复合逻辑的原子性。比如“若小于阈值才加 1”,不能靠两次原子操作拼出来,否则竞态照旧。
-
atomic.AddInt64适合纯递增/递减、状态标记翻转(如atomic.StoreInt32(&ready, 1)) - 不要用
atomic.LoadInt64+atomic.StoreInt64模拟条件更新——这是典型错误 - 32 位系统上对
int64做原子操作,必须确保变量地址 8 字节对齐,否则 panic;Go 1.17+ 在 struct 中自动填充,但裸变量需手动对齐或直接用atomic.Int64
atomic.Int64 类型比 raw int64 + 函数调用更安全
Go 1.19 引入的 atomic.Int64 是封装类型,自带方法如 .Add()、.Load()、.CompareAndSwap(),相比老式 atomic.AddInt64(&x, 1) 更不易出错:
- 避免传错地址(
&x写成x会导致编译失败) - 方法调用天然绑定到该变量,不会意外操作其他 int64 变量
- 支持
CompareAndSwap等高级操作,且类型安全(比如不能对atomic.Int64调用StoreUint64)
示例:
var counter atomic.Int64 counter.Add(1) // ✅ 安全、简洁 counter.CompareAndSwap(0, 1) // ✅ 返回 bool,语义清晰 // 不再需要 atomic.LoadInt64(&counter) —— 直接 counter.Load()
计数器需要线程安全读取时,别漏掉 memory ordering
Go 的 atomic 操作默认提供 sequential consistency(顺序一致性),但实际中常被忽略的是:普通变量赋值和原子操作之间没有自动同步。比如:
var total int64
var counter atomic.Int64
go func() {
counter.Add(1)
total = 42 // 这行可能被重排序到 counter.Add 之前!
}()
如果另一个 goroutine 先读 total 再读 counter.Load(),可能看到 total == 42 但 counter == 0。解决方式只有两个:
- 全部用原子操作(
total也声明为atomic.Int64) - 用
atomic.StoreInt64/atomic.LoadInt64显式同步,并理解它们附带的 memory barrier 效果
别依赖“看起来顺序对”——CPU 和编译器都可能重排。
限流类计数器慎用 CompareAndSwap 实现“过期重置”
常见误写:
if atomic.LoadInt64(&reqCount) > 100 {
atomic.StoreInt64(&reqCount, 0) // ❌ 竞态:两 goroutine 同时判断 >100,都重置
}
正确做法是用 CAS 循环尝试:
for {
old := atomic.LoadInt64(&reqCount)
if old
<p>但注意:高并发下这种自旋可能浪费 CPU;若重置逻辑复杂(比如还要记录时间戳),建议退回到 <code>sync.Mutex</code> 或改用 <code>sync/atomic</code> + <code>time.Now()</code> 分桶计数。</p>
真正的难点不在语法,而在边界——比如一个计数器既要高速递增,又要按分钟清零,还要支持外部查询当前速率。这时候 atomic 单独撑不住,得配合 channel 控制重置时机,或用 <code>atomic.Value</code> 交换整个统计结构体。别把所有逻辑塞进一个原子变量里。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











