int 不能直接用作并发计数器,因为其自增(++)和赋值非原子操作,多 goroutine 同时执行 counter++ 会导致丢失更新,如 100 个 goroutine 各加 1,结果远小于 100 且每次不同。

为什么不能直接用 int 做并发计数器
因为 int 的自增(++)和赋值不是原子操作,多个 goroutine 同时执行 counter++ 会导致丢失更新。现象是:启动 100 个 goroutine 各自加 1,最终结果远小于 100,且每次运行结果还不一致。
常见错误写法:
var counter int
for i := 0; i < 100; i++ {
go func() {
counter++ // ❌ 竞态,go run -race 会报 data race
}()
}
根本原因:读取当前值 → 加 1 → 写回,三步之间可能被其他 goroutine 插入操作。
用 sync.Mutex + 匿名函数封装最简方案
把锁和变量一起闭包进匿名函数,对外只暴露安全的增减接口,避免外部误操作。这是兼顾简洁性和安全性的实用做法。
立即学习“go语言免费学习笔记(深入)”;
-
sync.Mutex开销小,对低频计数(如请求统计、错误计数)完全够用 - 匿名函数捕获
mu和val,形成独立作用域,不污染全局 - 返回的闭包函数类型为
func() int或func(int),调用方无需关心锁逻辑
示例:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
newCounter := func() func(int) int {
var mu sync.Mutex
var val int
return func(delta int) int {
mu.Lock()
defer mu.Unlock()
val += delta
return val
}
}
counter := newCounter()
go counter(1)
go counter(1)
// 调用后 val 一定准确
atomic.Int64 在什么情况下比 Mutex 更合适
当计数器被高频读写(比如每秒数万次),且只做加减、读取等简单操作时,atomic.Int64 是更优选择 —— 无锁、零分配、性能更高。
-
atomic.Int64.Add()和atomic.Int64.Load()是原子的,不用 defer 或显式 unlock - 不能直接用
++,必须调用.Add(1),否则失去原子性 - 注意:Go 1.19+ 才支持
atomic.Int64;旧版本需用atomic.AddInt64(&v, 1)配合int64变量
典型用法:
var counter atomic.Int64
for i := 0; i < 100; i++ {
go func() {
counter.Add(1) // ✅ 安全
}()
}
fmt.Println(counter.Load()) // 一定输出 100
别忽略初始化和重置场景的并发问题
很多实现只考虑了“加”,但实际中常需要“重置为 0”或“设为指定值”。如果重置没加锁或没用原子操作,就会破坏一致性。
- 用
sync.Mutex封装时,重置也要在锁内完成:val = 0 - 用
atomic.Int64时,重置不能写val = 0(破坏原子性),而要用Store(0) - 如果计数器要支持 GetAndReset(获取当前值并归零),必须用锁,因为
Load()+Store(0)不是原子组合
例如错误重置:
counter = 0 // ❌ 即使是 atomic.Int64,也不能直接赋值
正确写法:counter.Store(0)。

















