atomic.addint64 安全,counter++ 必然竞态:因拆为读-加-写三步,多 goroutine 可能同时读旧值导致丢失更新;必须全量使用 atomic 函数操作 int64 变量,混用普通读写将破坏内存可见性与正确性。

直接用 atomic.AddInt64 就行,别碰 counter++,否则结果大概率错——这不是概率问题,是必然竞态。
为什么 counter++ 一定不行?
它在底层拆成三步:读值 → 加1 → 写回。两个 goroutine 同时执行,完全可能读到同一个旧值,各自加1后都写回去,结果只+1而不是+2。
- Go 的
-race检测器会立刻报Data Race - 不加
-race也可能跑出随机结果(比如 1000 次并发只得到 987) - 哪怕只有一处用了
counter++,整个变量就失去原子性保障
atomic.AddInt64 怎么用才安全?
它是一条 CPU 原子指令,没有中间状态,也不依赖锁调度。但必须注意几个硬性条件:
- 变量类型必须是
int64(别用int,32/64 位平台行为不一致) - 传参必须是地址:
&counter,传值会编译失败 - 所有读写操作都得走 atomic 函数:读用
atomic.LoadInt64(&counter),别直接读变量 - 全局变量或堆上分配的变量自动对齐;结构体里紧挨着其他字段的
int32字段可能不对齐,尤其在 ARM 上会 panic
示例:
var counter int64<br>go func() {<br> atomic.AddInt64(&counter, 1)<br>}()<br>// ...<br>fmt.Println(atomic.LoadInt64(&counter))
什么时候该换 sync.Mutex?
atomic 只适合单个基础类型的简单操作。一旦出现这些情况,就得切回锁:
- 要同时更新多个变量(比如计数器 + 时间戳 + 状态码)
- 逻辑不是“纯增减”,而是带判断的复合操作(如“如果小于阈值才加1”)
- 需要阻塞等待(比如等计数器达到某值再继续)
- 你要操作的是
float64、map或结构体——atomic不支持,得用atomic.Value或锁
CAS(CompareAndSwap)不是炫技,是真有用
比如服务启动只允许一次,用 atomic.CompareAndSwapInt32(&state, 0, 1) 比加锁干净得多。但它不是万能的:
- 失败不重试就是“尽力而为”,需要循环重试时得自己加
for+runtime.Gosched()防止忙等 - 读旧值必须用
atomic.LoadInt32,不能缓存局部变量,否则 CAS 总是失败 -
int64版本的 CAS 在 32 位系统上开销略大,高频场景优先用int32
最常被忽略的一点:只要开始用 atomic,这个变量的所有访问就必须走 atomic 函数。混用普通赋值或读取,内存可见性和顺序保证就全没了——这不是性能问题,是正确性崩塌。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











