用 atomic.AddInt64 即可实现轻量并发安全计数,但仅适用于单操作原子性;涉及“先读后改”等复合逻辑时必须换 sync.Mutex,多 key 统计需配锁或分片 map。
用
就够了,别碰
Go 里最轻量、最常用的并发安全计数器,就是靠
包里的原子操作。它不加锁、无调度开销,CPU 指令级保证单次读写或增减的完整性。
常见错误现象:
然后多个 goroutine 同时执行
,最终值远小于预期(比如 1000 个 goroutine 各加 1,结果只有 200),
会立刻报
。
必须用
类型(不是
),否则在 32 位系统或某些架构上可能 panic
所有读写都得走原子函数:增用
,读用
,写用
,混用普通赋值(如
)会导致未初始化读取到垃圾值
结构体中
字段要自然对齐——稳妥起见,把它放在结构体第一个字段
返回新值;传负数(如
)可实现减法,但业务上是否允许减、是否需防负,得由上层逻辑控制,原子包不负责校验
什么时候必须换
?
一旦操作里出现“先读再决定怎么改”,
就不够用了——它只保单条指令原子性,不保逻辑原子性。
使用场景举例:限流计数器(超过阈值就拒绝)、状态机切换(仅当当前为
才能设为
)、或需要在增减前后做日志/通知等副作用。
立即学习
“
go语言免费学习笔记(深入)
”;
是典型错误:两次原子调用之间,其他 goroutine 可能已把值改掉
这种复合判断 + 修改,要么用
加循环重试(适合冲突低、逻辑极简),要么直接上
开销在百万级/秒计数下才明显,日常 HTTP 请求计数、错误统计等完全扛得住,且代码更直白、易维护
要统计不同 key 的次数,别裸用 map
多个 goroutine 同时往一个
写,运行时必 panic:
。这不是概率问题,是 Go 主动中止程序防止数据损坏。
实操建议优先选
+ 普通 map:
读多写少(比如每秒查 1000 次状态,只写 10 次)时,
允许多读并发,比普通
更高效
不是银弹:它不支持
,遍历必须用
回调,值类型是
,还得类型断言;键频繁增删或需全量快照时,反而不如带锁的普通 map 清晰
如果 key 数量极大(千万级)、写压力均匀,可考虑分片 map(sharded map),但多数业务用不到
高频调用时三个容易被忽略的细节
哪怕你全用对了
,高并发下仍可能出问题——不是功能错,而是行为不可控或性能反降。
初始化必须用
,不能直接
;跨包导出变量尤其危险,其他 goroutine 可能读到未写入完成的中间态
别在 for 循环里反复
判断条件再
,这本质是自旋,浪费 CPU,还可能饿死其他 goroutine;该用
或 channel 控制节奏就别硬刚原子操作
哈希类计数器(如 Counting Bloom Filter)里,别复用同一个
实例;
每次都要新建,否则多个 goroutine 并发
会导致哈希值错乱——这是静默错误,很难排查
复杂点不在“会不会用”,而在于“什么时候不该用”。原子操作不是万能开关,它只解决“单变量单操作”的并发安全;只要逻辑里带上 if、for、多个字段联动,就得退回到锁或更高层同步机制。
atomic.AddInt64counter++sync/atomicvar counter intcounter++go run -raceData raceint64intatomic.AddInt64atomic.LoadInt64atomic.StoreInt64c.val = 0int64atomic.AddInt64-1type Counter struct {
val int64
}
func (c *Counter) Inc() { atomic.AddInt64(&c.val, 1) }
func (c *Counter) Get() int64 { return atomic.LoadInt64(&c.val) }sync.MutexatomicRunningStoppingif counter > 100 { atomic.AddInt64(&counter, -1) }atomic.CompareAndSwapInt64sync.Mutexsync.Mutexmap[string]int64fatal error: concurrent map writessync.RWMutexRWMutexMutexsync.MaprangeRange()interface{}type KeyCounter struct {
mu sync.RWMutex
data map[string]int64
}
func (c *KeyCounter) Inc(key string) {
c.mu.Lock()
c.data[key]++
c.mu.Unlock()
}
func (c *KeyCounter) Get(key string) int64 {
c.mu.RLock()
defer c.mu.RUnlock()
return c.data[key]
}atomicatomic.StoreInt64(&counter, 0)counter = 0atomic.LoadInt64AddInt64sync.WaitGrouphash.Hashfnv.New64()Write()