不能直接用普通变量做并发计数,因为count++非原子操作(读-改-写三步),会导致数据竞争;应使用sync/atomic包的原子操作,如atomic.AddInt64和atomic.LoadInt64,且所有读写必须统一走原子操作。
多个 goroutine 同时对一个 int 变量执行 ++,结果大概率比预期小。这不是“偶尔出错”,而是因为 count++ 实际包含三步:读取、加1、写回——中间可能被其他 goroutine 打断。Go 的竞态检测器(go run -race)会立刻报出 Data race 错误。
适用于单个数值的增减、比较交换等简单操作,无锁、零分配、性能接近汇编。注意类型必须严格匹配: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/atomic 只能处理单个值的简单操作。一旦统计逻辑变复杂——比如要同时更新计数器和记录最后更新时间、或需要条件重置、或要聚合多个字段(成功数/失败数/平均耗时)——就必须升级到 sync.Mutex 或 sync.RWMutex。
atomic,可读性和正确性优先sync.RWMutex 提升并发读性能
或换 sync.Map原生 map 非并发安全。直接在多个 goroutine 里 m[key]++ 会 panic:fatal error: concurrent map writes。
sync.Mutex 包裹 map 操作最直观sync.Map,但它不支持遍历和 len(),且内部有额外指针跳转开销map + atomic 混合——atomic 对 map 本身无效,只对指针或整数有用容易被忽略的一点:哪怕你只读 map,只要其他 goroutine 在写,也必须加锁(或使用 sync.Map 的 Load 方法)。Go 不保证非同步读的内存可见性。