不能直接用 int64 变量做并发自增,因为 i++ 非原子操作,需用 sync/atomic.AddInt64 实现无锁安全递增;注意类型必须为 int64、变量地址需稳定、避免非原子读写及溢出风险。
为什么不能直接用 int64 变量做并发自增
多个 goroutine 同时执行 i++ 会出错,因为这不是原子操作:读取、加 1、写回三步可能被交叉打断。即使用 sync.mutex 能解决,但锁开销大,尤其在高并发 id 生成场景下容易成为瓶颈。
Go 的 sync/atomic 提供了无锁的底层原子指令,适用于简单计数类 ID(如单调递增、不关心全局唯一性细节的请求 ID、日志序列号等)。
atomic.AddInt64 是最常用且安全的选择
它对 *int64 执行原子加法并返回新值,底层映射为 CPU 的 LOCK XADD 指令(x86)或等价的内存屏障操作,无需锁,性能接近普通变量访问。
- 必须用
int64类型 ——int在 32 位系统上是 32 位,atomic.AddInt64不接受它 - 变量需声明为包级或全局变量,或确保其地址稳定(不能是栈上逃逸后被回收的局部变量)
- 初始化建议从
0或1开始,避免负数 ID 引发下游解析问题
var idCounter int64 = 0
func NextID() int64 {
return atomic.AddInt64(&idCounter, 1)
}
注意 atomic.LoadInt64 和 atomic.StoreInt64 的使用边界
如果需要“读取当前值”或“重置计数器”,必须用对应原子读/写函数,不能直接读写变量 —— 否则破坏内存可见性保证,可能读到陈旧值。
-
atomic.LoadInt64(&idCounter)用于安全读取当前值(如监控上报) -
atomic.StoreInt64(&idCounter, 0)可重置,但要注意:这会破坏单调性,仅适用于明确允许重置的场景(如每小时清零的统计 ID) - 不要混用非原子操作:比如
idCounter = 100是危险的,会导致数据竞争
ID 值溢出和类型转换的实际风险
int64 最大约为 9.2e18,看似够用,但在高频服务中几周就可能耗尽(比如每微秒生成一个 ID)。更现实的问题是:很多地方期望 ID 是 string 或 uint64,而 atomic 不提供 uint64 版本的加法(只有 Load/Store)。
- 若需
uint64,只能用atomic.LoadUint64+atomic.StoreUint64手动 CAS 循环实现加法,代码更复杂且易错 - 转
string推荐用strconv.FormatInt(val, 10),别用fmt.Sprintf—— 前者无内存分配,后者在 hot path 上易触发 GC - 溢出行为是绕回负数(
math.MaxInt64 + 1 == math.MinInt64),不会 panic,需业务层自行判断是否接近上限
真正需要全局唯一、抗重放、带时间戳或机器标识的 ID(比如分布式订单号),atomic 单一计数器远远不够 —— 那得上 github.com/google/uuid、sony/flake 或自研 snowflake 变种。


















