atomic.addint64可实现零锁计数器,但仅适用于纯增减读取;需严格使用int64类型、显式初始化、用atomic.loadint64读取;涉及条件更新或多字段联动时必须用sync.mutex。

用 atomic.AddInt64 实现零锁计数器
只要只是增减、读取整数,atomic 是最轻量且绝对安全的选择。它底层靠 CPU 指令保证原子性,没有锁开销,也不用担心协程调度打断。
常见错误是把 int 当作 int64 传给 atomic 函数——Go 的 atomic 系列对类型极其敏感,int 在 32 位系统上是 4 字节,int64 是 8 字节,混用会 panic:panic: sync/atomic: unaligned 64-bit operand。
- 始终用
int64类型声明计数器变量,哪怕你只计到 100 - 读取用
atomic.LoadInt64(&counter),别直接读变量(可能看到脏值) - 自增用
atomic.AddInt64(&counter, 1),不是counter++ - 初始化必须显式赋值,比如
var counter int64 = 0,不能依赖零值隐式声明后直接传地址
var counter int64 = 0
go func() { atomic.AddInt64(&counter, 1) }()
go func() { atomic.AddInt64(&counter, 1) }()
// 最终 counter 一定是 2,无竞争
什么时候非得用 sync.Mutex 而不是 atomic
atomic 只能做简单操作:加、减、存、取、比较并交换(CAS)。一旦逻辑涉及「读-改-写」多步,比如「如果计数小于阈值才加 1」,或者要同时更新多个关联变量,atomic 就不够用了。
典型误用是试图用多次 atomic.LoadInt64 + if + atomic.AddInt64 模拟条件更新——这中间存在竞态窗口,两个协程可能同时读到相同值、同时通过判断、同时加 1,导致超限。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 需要条件判断再更新时,必须用
sync.Mutex包住整个逻辑块 - 要支持重置(
Set(0))、获取并清零(Swap(0))等非标准操作,atomic没有对应函数,得自己封装互斥逻辑 - 如果计数器要附带日志、通知或与其他状态联动,Mutex 提供的临界区更可控
var mu sync.Mutex
var counter int64
func IncIfLessThan(limit int64) bool {
mu.Lock()
defer mu.Unlock()
if counter
<h3>
<code>sync/atomic</code> 和 <code>sync.Mutex</code> 的性能与逃逸差异</h3>
<p>单纯比吞吐:100 万次自增,<code>atomic.AddInt64</code> 通常比 <code>Mutex.Lock/Unlock</code> 快 5–10 倍,且不触发堆分配;而 Mutex 在争抢激烈时还会进入操作系统级休眠,延迟不可控。</p>
<p>但真实瓶颈往往不在计数器本身。容易被忽略的是:如果计数器变量本身逃逸到堆上(比如作为结构体字段被导出),或者被闭包捕获,GC 压力会间接上升——这时优化 <code>atomic</code> 写法意义不大,得先看逃逸分析。</p>
- 用
go build -gcflags="-m" main.go确认计数器是否逃逸;没逃逸就还在栈上,最理想 -
atomic操作不会造成逃逸,但包裹它的函数如果返回指针或接口,可能连带让计数器变量逃逸 - Mutex 字段即使没被并发使用,只要结构体被传入接口(如
io.Writer),也大概率逃逸
别在 map 里直接存 int64 当计数器用
很多人图省事,用 map[string]int64 存键值对计数,然后在多协程里直接 m[key]++——这是典型的并发写 panic,错误信息是:fatal error: concurrent map writes。
根本原因:Go 的 map 本身不是并发安全的,m[key]++ 展开是「读 + 计算 + 写」三步,中间任何一步都可能被其他协程打断。
- 不要幻想「只读 key 不写 map 结构」就安全;map 内部有哈希表扩容、桶迁移等隐藏写操作
- 如果必须按 key 分计数,要么给每个 key 配独立的
atomic.Int64(需预设 key 集合),要么用sync.Map(仅适合读多写少,且 value 必须是值类型) - 更通用的做法是用
sync.Mutex锁整个 map,或拆成多个分片 map + 分片锁降低争抢
atomic.Int64 就够了;真遇到条件更新或复合状态,别硬套 atomic,老老实实上 Mutex。最难的不是选哪个,而是意识到「看似简单的自增」背后,到底有没有隐藏的业务约束。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










