atomic.swapuint64严格按“读旧值→写新值→返回旧值”三步原子执行,无条件替换,不带判断;误当compareandswap用会导致逻辑崩坏,适用重置计数器、切换状态等场景。

atomic.SwapUint64 是原子交换,不是赋值或比较
它严格按「读取旧值 → 写入新值 → 返回旧值」三步原子执行,中间不会被其他 goroutine 打断。别把它当 atomic.StoreUint64 用——后者只写不返回;也别误以为它带条件(像 atomic.CompareAndSwapUint64),它无条件替换。
常见错误是:想“如果旧值是 X 就换成 Y”,却直接调用 atomic.SwapUint64,结果旧值被无声覆盖,逻辑崩了。
- 适用场景:实现无锁计数器重置、切换状态标志位(如从 running → stopping)、交换指针(配合
unsafe.Pointer) - 参数顺序固定:
atomic.SwapUint64(&addr, newval),第一个必须是指向uint64的指针,不能是变量名或转换后的地址 - 注意对齐:
uint64在 32 位系统上若未 8 字节对齐(比如嵌在 struct 里且前面是int32),会 panic:“unaligned 64-bit atomic operation”
为什么必须用 *uint64,不能传值或转类型
atomic.SwapUint64 底层依赖 CPU 的 CAS 指令,需要内存地址。传值(如 atomic.SwapUint64(x, 1))编译直接报错;用 uintptr 或 unsafe.Pointer 强转地址再转回,不仅绕过类型检查,还可能因 GC 移动对象导致悬空指针。
正确写法只有一种:var x uint64; atomic.SwapUint64(&x, 123)。struct 成员要交换时,确保该字段单独对齐,或整个 struct 用 //go:align 8 注释(Go 1.22+ 支持)。
- 错误示例:
atomic.SwapUint64((*uint64)(unsafe.Pointer(&someStruct.field)), 42)—— 危险,且 field 可能未对齐 - 安全替代:把要交换的
uint64提出来作为独立字段,或用sync/atomic提供的Value(适用于任意类型,但有额外分配开销) - 交叉编译时特别留意:ARM32 默认不支持 unaligned 64-bit 原子操作,哪怕你加了
//go:align,运行时仍可能 panic
和 CompareAndSwapUint64 的关键区别在哪
atomic.SwapUint64 总是成功并返回旧值;atomic.CompareAndSwapUint64 只有旧值匹配才写,返回 bool 表示是否成功。两者性能接近,但语义完全不同。
典型误用:用 Swap 实现“如果为 0 才设为 1”的开关,结果并发下多个 goroutine 全部写入,开关失去意义。
- 需要条件更新 → 用
atomic.CompareAndSwapUint64(&flag, 0, 1) - 需要获取旧值并强制更新 → 用
old := atomic.SwapUint64(&counter, 0)(比如归零计数器并拿到之前值) - 性能提示:在高竞争场景下,
CompareAndSwap可能因失败重试而略慢;Swap是单指令,更稳定
实际写法示例:安全重置带返回的计数器
下面是一个线程安全的计数器,支持并发读、原子交换归零:
type Counter struct {
val uint64
}
func (c *Counter) Add(delta uint64) {
atomic.AddUint64(&c.val, delta)
}
func (c *Counter) Reset() uint64 {
return atomic.SwapUint64(&c.val, 0)
}
// 使用:
// var cnt Counter
// cnt.Add(5)
// old := cnt.Reset() // old == 5, c.val == 0
注意:这个 Reset 不保证「恰好一次归零」,它只是原子交换。如果业务要求「首次调用才生效」,就得换 CompareAndSwapUint64 加状态标记。
真正容易被忽略的是内存模型隐含约束:SwapUint64 自带 acquire-release 语义,它前后的内存读写不会被编译器或 CPU 重排——这点常被忽视,却决定了你能否靠它同步其他非原子字段。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











