必须用 atomic.addint64(或 atomic.int64.add)实现线程安全自增,因 id++ 是非原子的读-改-写操作;atomic.load/store 仅适用于读取后条件更新场景,不适用于计数器。

Go 的 atomic 包能安全实现全局自增 ID,但必须用 atomic.AddInt64(或对应整数类型)配合初始化为 0 的变量,不能直接对 int 变量做原子读写。
为什么不能直接用 atomic.LoadInt64(&id) 然后 id++
这是最常见的误用:把原子操作当成“带锁的普通变量”。id++ 是非原子的读-改-写三步操作,即使 id 是 int64 类型,也不保证线程安全。Go 的 atomic 包不提供 ++ 运算符封装,所有递增必须显式调用 atomic.AddInt64。
-
atomic.LoadInt64和atomic.StoreInt64只适合“读取后决定是否覆盖”的场景(如配置热更新),不适合计数器 - 想拿到新值并自增,必须用
atomic.AddInt64(&counter, 1)—— 它返回的是自增后的值 - 如果需要从 1 开始编号,初始值设为 0,首次调用
atomic.AddInt64(&id, 1)就得到 1
正确初始化和使用 atomic.Int64(Go 1.19+ 推荐)
Go 1.19 引入了泛型原子类型 atomic.Int64,比裸指针操作更安全、可读性更好,且编译期能检查类型匹配。
- 声明:
var id atomic.Int64(自动初始化为 0) - 获取并自增:
nextID := id.Add(1)(返回新值) - 只读取当前值:
current := id.Load() - 注意:
id.Add(1)是线程安全的,无需额外同步;但不要混用id.Store(x)手动设值,除非有明确重置逻辑
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var id atomic.Int64
var wg sync.WaitGroup
for i := 0; i
<h3>兼容旧版本(Go int64 指针 + <code>atomic.AddInt64</code>
</h3>
<p>老版本没有 <code>atomic.Int64</code>,只能用原始类型加指针。关键点是:变量必须是 <code>int64</code>(不是 <code>int</code>),且所有操作都通过 <code>atomic.</code> 函数进行。</p>
- 错误写法:
var id int→atomic.AddInt64(&id, 1)编译失败(类型不匹配) - 正确写法:
var id int64→atomic.AddInt64(&id, 1) - 如果 ID 范围确定很小(比如永远 ≤ 2³¹−1),仍建议用
int64:避免 32 位系统上int是 32 位导致atomic.AddInt32和AddInt64混用出错 - 性能上,
atomic.AddInt64在 64 位机器是单指令,无锁,比sync.Mutex快一个数量级
容易被忽略的边界问题
全局自增 ID 看似简单,但实际部署时几个点常被跳过:
- 服务重启后 ID 重置为 0 —— 如果业务要求全局唯一且持久(如订单号),
atomic本身无法解决,得结合数据库序列或分布式 ID 生成器(如 Snowflake) - 多个 Go 进程(非 goroutine)之间不共享内存,每个进程有自己的
atomic变量 —— 此时“全局”仅限单进程内 - 高并发下
atomic.AddInt64无性能瓶颈,但若在循环里频繁调用并拼接字符串(如"order_" + strconv.FormatInt(id.Add(1), 10)),GC 压力可能成为瓶颈,建议预分配或用fmt.Sprintf缓冲池











