sync/atomic 与 CAS 是什么?何时用它而不是加锁?
结论先行
sync/atomic 提供无锁的原子原语(Add/Load/Store/Swap/CompareAndSwap),在硬件层保证单个变量的原子读改写与可见性;CAS(Compare And Swap)是“读-比-写”一步完成的核心原语,也是各种无锁结构的地基。取舍原则:只操作单一标量、没有复杂临界区时用 atomic;涉及多个变量的一致状态或长临界区,老实加锁。
要点
- 常用操作:AddT/LoadT/StoreT/SwapT/CompareAndSwapT 覆盖数值与指针;现代版本还有泛型封装 atomic.Int64、atomic.Pointer[T] 等。
- 语义强度:atomic 操作具有全局一致的顺序语义并参与 happens-before:Load 能看到此前所有 Store 的效果。
- CAS 语义:地址当前值等于 old 才写入 new,返回是否成功;失败说明被别人抢先,通常配合循环重试做乐观更新。
- 性能直觉:无锁无系统调用,低争用下远快于 Mutex;但多核高争用会让同一缓存行在各核间反复失效(缓存行乒乓),可能比锁还慢。
- 与锁的取舍:atomic 只保证“一个变量”的原子读改写;锁能保护任意多变量的临界区,并提供排队等待语义。
- 经典用途:计数器、开关标志、限流令牌、无锁栈与队列;无锁结构要额外处理 ABA 问题(用版本号或更复杂的方案)。
- 对齐要求:32 位平台上对 64 位变量的原子操作要求 8 字节对齐(放结构体首字段或单独分配),否则结果不可靠。
示例
var n atomic.Int64
n.Add(1) // 并发安全累加
for { // CAS 乐观更新:失败就重读重试
cur := n.Load()
if n.CompareAndSwap(cur, cur+1) {
break
}
}
常见追问/记忆点
- 追问:CAS 的 ABA 问题是什么?答:值从 A 变成 B 又变回 A,CAS 误判“从未被修改”;用版本号或戳记解决。
- 追问:什么时候 atomic 反而慢?答:多核高争用时缓存行持续失效,总线流量剧增,吞吐跌到锁之下。
- 追问:atomic 能替代锁吗?答:只对单变量可以;跨多变量的一致性约束必须用锁或精心设计。
- 记忆:单变量用 atomic、多变量状态机用锁;CAS 失败要循环重试。