不能直接用普通变量做并发计数,因为count++非原子操作(读-改-写三步),会导致数据竞争;应使用sync/atomic包的原子操作,如atomic.AddInt64和atomic.LoadInt64,且所有读写必须统一走原子操作。

为什么不能直接用普通变量做并发计数
多个 goroutine 同时对一个 int 变量执行 ++,结果大概率比预期小。这不是“偶尔出错”,而是因为 count++ 实际包含三步:读取、加1、写回——中间可能被其他 goroutine 打断。Go 的竞态检测器(go run -race)会立刻报出 Data race 错误。
sync/atomic 是最轻量的正确解法
适用于单个数值的增减、比较交换等简单操作,无锁、零分配、性能接近汇编。注意类型必须严格匹配:int64 不能用 atomic.AddInt32,uint32 不能用 atomic.AddUint64。
- 计数器必须声明为
int64(即使你只计到 100),因为atomic对int没有通用函数,且int在 32 位系统上非原子 - 用
atomic.AddInt64(&counter, 1)替代counter++;用atomic.LoadInt64(&counter)读值,不要直接读变量 - 避免混用:一旦用了
atomic,所有读写都必须走原子操作,否则仍存在数据竞争
package main
import (
"fmt"
"sync/atomic"
"time"
)
func main() {
var counter int64 = 0
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 1000; j++ {
atomic.AddInt64(&counter, 1)
}
}()
}
wg.Wait()
fmt.Println("Final count:", atomic.LoadInt64(&counter)) // 总是 100000
}
什么时候该换 sync.Mutex 而不是硬套 atomic
sync/atomic 只能处理单个值的简单操作。一旦统计逻辑变复杂——比如要同时更新计数器和记录最后更新时间、或需要条件重置、或要聚合多个字段(成功数/失败数/平均耗时)——就必须升级到 sync.Mutex 或 sync.RWMutex。
- 别为了“看起来快”强行拆解逻辑去适配
atomic,可读性和正确性优先 - 如果读多写少(如高频查询当前统计值,低频更新),用
sync.RWMutex提升并发读性能 - Mutex 保护的是临界区代码段,不是某个变量;锁粒度要尽量小,但别细到每行都加锁
map 并发读写必须加锁或换 sync.Map
原生 map 非并发安全。直接在多个 goroutine 里 m[key]++ 会 panic:fatal error: concurrent map writes。
立即学习“go语言免费学习笔记(深入)”;
- 若 key 数量可控、读写频率均衡,用
sync.Mutex包裹 map 操作最直观 - 若 key 极多、读远多于写、且能接受弱一致性(如统计上报场景),可用
sync.Map,但它不支持遍历和 len(),且内部有额外指针跳转开销 - 千万别用
map+atomic混合——atomic 对 map 本身无效,只对指针或整数有用
容易被忽略的一点:哪怕你只读 map,只要其他 goroutine 在写,也必须加锁(或使用 sync.Map 的 Load 方法)。Go 不保证非同步读的内存可见性。










