不能直接用普通 int 做并发计数,因为 counter++ 是非原子的“读-改-写”三步操作,会导致数据竞争、计数值偏小、偶发 panic 或负数;应使用 sync/atomic.AddInt64 等原子操作,操作对象须为对齐的 int64 指针。
Go 语言中用 sync/atomic 实现并发安全计数器,比加锁更轻量、更高效,但必须严格遵循原子操作的使用边界——不能用在需要复合逻辑(如“读-改-写”非单条原子指令)的场景。
多个 goroutine 同时执行 counter++ 会导致数据竞争:该操作实际拆分为「读取值 → 加 1 → 写回」三步,中间可能被其他 goroutine 打断。Go 的 go run -race 会立刻报出 Data race 错误。
常见错误现象:
go build -race 检测到 Read at ... by goroutine N / Previous write at ... by goroutine M
这是最常用、最稳妥的并发计数方式。注意:操作对象必须是 int64 类型指针,且变量需对齐(全局变量或 struct 字段按 8 字节对齐即可)。
使用场景:
atomic.LoadInt64 实现无锁读取参数差异:
atomic.AddInt64(&counter, 1) 返回新值;atomic.AddInt64(&counter, -1) 可减func() { n := int64(0); atom
ic.AddInt64(&n, 1) }),逃逸分析可能引发隐患int 非原子安全,务必显式用 int64 并调用对应函数var counter int64
func increment() {
atomic.AddInt64(&counter, 1)
}
func get() int64 {
return atomic.LoadInt64(&counter)
}
// 启动 100 个 goroutine 并发调用 increment()
for i := 0; i < 100; i++ {
go increment()
}
time.Sleep(time.Millisecond)
fmt.Println(get()) // 稳定输出 100
当计数逻辑含判断(如“仅当当前值
容易踩的坑:
func incrementIfLessThan(max int64) bool {
for {
old := atomic.LoadInt64(&counter)
if old >= max {
return false
}
if atomic.CompareAndSwapInt64(&counter, old, old+1) {
return true
}
// CAS 失败,说明期间有别的 goroutine 改了值,重试
}
}
真正要注意的是内存顺序和对齐——atomic 操作默认提供 sequential consistency,够用;但若嵌套在复杂结构体中,要确保字段偏移是 8 的倍数,否则在某些架构上 panic。这点常被忽略,尤其当计数器和其他字段混排时。