Go 的 sync/atomic 包仅适用于基础类型(int32/int64/uint32/uintptr/*T/unsafe.Pointer)的简单操作,bool/float 需转换,复合类型须用 atomic.Value;其默认 relaxed 内存序不保证顺序,强同步需 Add 或 CAS;多数场景 mutex 更安全清晰。
Go 的 sync/atomic 包不是“替代 mutex 的万能方案”,而是为极少数特定场景设计的底层工具:仅当操作对象是基础类型(int32、int64、uint32、uintptr、*T、unsafe.Pointer)且逻辑足够简单时,才适合用原子操作。滥用反而会掩盖竞态、降低可读性,甚至因内存序误用引发隐蔽 bug。
哪些变量能用 atomic 安全修改?
只能对以下类型的变量直接调用 atomic 函数:
-
int32、int64、uint32、uint64、uintptr -
bool不行 —— 必须用int32模拟(0/1),再用atomic.LoadInt32/atomic.StoreInt32 -
float32/float64不行 —— 需转成uint32/uint64后用atomic.LoadUint32等,且需注意 IEEE 754 表示与平台字节序 - 结构体、切片、map、interface{} 等复合类型不能直接原子操作 —— 若需“原子更新整个结构”,应改用
atomic.Value(它内部用读写锁+指针替换实现)
atomic.LoadUint64 和 atomic.StoreUint64 的典型误用
看似安全的读写组合,在无同步约束下仍可能违反期望顺序。例如:
var counter uint64 // goroutine A: atomic.StoreUint64(&counter, 1) // goroutine B: v := atomic.LoadUint64(&counter) // 可能读到 0(如果 Store 还没完成可见)
这不是函数 bug,而是内存模型决定的:Go 的 atomic 默认提供“relaxed”内存序,不保证与其他内存访问的顺序。若需强顺序(如“写完立刻让所有 goroutine 看到”),必须配合 atomic.AddUint64 或显式使用 atomic.CompareAndSwapUint64 构建同步点。