跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

Golang怎么做并发安全的计数器_Golang并发计数器教程【最新】

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

相关文章