atomic操作仅在单一int64变量、无条件单调增减、无分支依赖、不构成业务不变式时可替代sync.Mutex;误用会导致竞态、panic或更高延迟。

atomic 操作不是“学完就快”的语法糖,它只在满足严格条件时才比 sync.Mutex 快且安全;盲目套用反而引入竞态、panic 或更差的延迟。
atomic.AddInt64 什么情况下能替代 sync.Mutex
- 操作目标必须是单一
int64变量(不能是 struct 字段、不能是切片元素) - 逻辑必须是无条件单调增减:比如累计请求数、错误计数、连接数
-
不能有分支判断依赖该值:写成
if atomic.LoadInt64(&c) > 100 { atomic.AddInt64(&c, -1) }是错的——两步之间完全竞态 -
不与其他变量构成业务不变式:比如“
c增加后必须同步更新cache[uid]”,这就得用锁
常见误用:
- 把
atomic.LoadInt64当作“轻量锁”来读+判+改 - 在 struct 里声明
count int64然后直接取地址传给atomic.AddInt64—— 32 位系统或字段填充不对齐时会 panic
atomic.CompareAndSwapInt64 必须配 for 循环
-
atomic.CompareAndSwapInt64返回false是常态,不是错误 - 单次调用失败就放弃,等于放弃状态更新(比如“仅首次设置为 1”永远卡住)
- 正确写法是显式循环重试,每次重新
Load当前值:
for {
old := atomic.LoadInt64(&state)
if old != 0 {
break // 条件已不满足,退出
}
if atomic.CompareAndSwapInt64(&state, old, 1) {
break
}
// 不 sleep,不 Gosched,除非你明确压测发现 CPU 占满
}注意:重试不是机械循环。如果失败意味着业务上下文已变(如订单已被取消),应重新校验业务状态,而非无脑重试。
atomic.Value 存指针,别存结构体值
-
atomic.Value.Store接收interface{},但必须传指针,例如&Config{Timeout: 30} - 若传值副本(
Config{...}),每次Load都会复制整个 struct,且无法保证内部字段原子性 - 它适合配置热更新、缓存实例替换等“写少读多”场景,但每次
Store都分配新对象,写频繁时 GC 压力会上升
容易忽略的点:atomic.Value 不提供对结构体字段的原子访问能力——它只保证“整个值的存/取”是原子的,内部字段仍可能被并发读写破坏。
立即学习“go语言免费学习笔记(深入)”;
int64 字段内存对齐问题在 32 位环境真实存在
- Go 1.19+ 推荐直接用
atomic.Int64类型(封装了对齐和方法),而不是裸int64+ 函数 - 如果必须用裸
int64:- 全局变量直接声明
var counter int64(编译器保证对齐) - struct 中需确保
int64字段位于第一个位置,或手动填充:_ [7]byte
- 全局变量直接声明
- 局部变量取地址传给原子函数(如
var n int64; go func() { atomic.AddInt64(&n, 1) }())风险极高:栈回收不可控、对齐不可靠
最省心的做法:用 atomic.Int64 替代裸 int64,用 atomic.Value 替代需要原子更新的结构体指针。



















