必须用sync.mutex而非atomic时,是当操作涉及“读-改-写”多步、跨字段协同或带条件判断的复合逻辑——atomic仅保单指令原子性,无法保证逻辑原子性。

直接用 atomic 还是 sync.Mutex,取决于你对共享变量的操作是否「可原子化」——不是看变量类型,而是看逻辑是否能被压缩成一条不可拆分的指令。
什么时候必须用 sync.Mutex 而不能用 atomic
只要操作涉及「读-改-写」多步、跨字段、或带条件判断,atomic 就失效了。它不是慢,是根本做不到。
if atomic.LoadInt64(&counter) → 两个 goroutine 可能同时读到 99,都通过判断,最终 counter = 101(超限)- 结构体里有
count int64和lastUpdated time.Time,要一起更新 →atomic无法保证这两个字段的修改“同步可见” - 需要支持
Reset()、SwapAndAdd()或记录日志 → 没有对应原子函数,必须自己加锁封装 - 变量是
map[string]int或[]byte→atomic不支持这些类型,连地址都不能传
sync/atomic 的硬性使用前提
用错会 panic 或读到脏值,不是性能问题,是正确性问题。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 计数器变量必须声明为
int64(哪怕只计到 5),不能是int;32 位系统上int是 4 字节,atomic.AddInt64会因内存未对齐直接 panic - 读取必须用
atomic.LoadInt64(&counter),不能直接读counter—— 编译器可能优化掉重读,看到过期值 - 初始化必须显式赋值:
var counter int64 = 0,不能只写var counter int64后就传地址给atomic函数 - Go 1.21+ 推荐用
atomic.Int64类型,它内部已做 cache-line 对齐,避免 false sharing;老版本需手动填充字节
性能差异的真实边界在哪
别只看“atomic 快 10 倍”的 benchmark 结论——那是在理想隔离条件下测的。真实服务里,瓶颈往往不在计数器本身。
- 如果写操作占比超过 15%,且 goroutine 数远超 CPU 核数,
atomic.AddInt64会持续自旋抢缓存行,反而比Mutex更耗资源 - 用
go run -race跑起来再快也没用:竞态检测器会报出所有漏锁路径,而atomic错误用法不会被 race detector 捕获(它只检查非原子读写) -
atomic.Value适合读多写少的整个结构体替换(比如配置热更),但它每次写都要分配新对象;高频写场景下,堆分配压力可能盖过锁开销 - 如果计数器嵌在导出结构体里(如
type Stats struct { Requests int64 }),该字段大概率逃逸到堆,这时锁和原子操作的 GC 开销差异就变小了
最常被忽略的一点:atomic 不提供内存屏障之外的任何同步语义。你靠它读到一个数字,不代表此时其他字段也处于一致状态——这个认知偏差,比选错工具更容易导致线上 bug。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










