Go atomic.Int64 Add 返回值如何用于无锁序号生成
来源:17golang原创
时间:2026-09-14 17:44:19 391浏览 收藏
如果多个 goroutine 需要领取业务序号,最小写法不是“先 Load 再 Add”,而是直接使用 atomic.Int64.Add(1) 的返回值。这个返回值是加法完成后的新值,因此每次调用都能拿到一个不会重复的序号;代码层面不需要 sync.Mutex。但它只保证原子递增,不保证 goroutine 的启动顺序、完成顺序,也不适合直接当作跨进程全局 ID。
Add(delta)返回新值,Add(1)可直接作为下一个序号。- 原子唯一不等于业务有序;并发任务可能先拿到 2、后完成 1。
- 不要复制已经使用过的
atomic.Int64,还要提前规划溢出和失败重试。
最小配方:让 Add(1) 直接产出新序号
atomic.Int64 的零值就是 0。第一次调用 Add(1) 返回 1,第二次返回 2。不要把它写成先读取再普通相加,因为那会把“读取、计算、写回”拆成多个并发步骤。
package sequence
import "sync/atomic"
// IDGenerator 只保存下一个可分配序号的计数状态。
type IDGenerator struct {
next atomic.Int64
}
// Next 返回本次调用独占的新序号,从 1 开始。
func (g *IDGenerator) Next() int64 {
// Add 返回递增后的值,正好就是本次领取的序号。
return g.next.Add(1)
}
这个结构体可以作为服务对象的字段长期持有。方法接收指针,避免把包含原子状态的对象复制一份。图 1 用静态关系框图展示了 IDGenerator、atomic.Int64、Add(1) 与返回序号之间的边界关系。

为什么不要用 Load 加一再 Store
下面这种写法每一步单独看都像是原子操作,组合起来却不是一次不可分割的递增:
// 错误示例:两个 goroutine 可能同时读到相同旧值。 old := g.next.Load() newValue := old + 1 // 这里没有把计算和写回绑定成一个原子动作。 g.next.Store(newValue) return newValue
两个调用可能同时读到 7,然后都计算出 8,最终返回重复的编号。Add(1) 把加法和返回值绑定在同一个原子操作中,调用者不需要自己拼接临界区。Go 官方文档还说明,原子操作在程序中表现为顺序一致的操作序列;这解决的是共享计数状态的并发可见性,不是整个业务流程的事务性。
Add 返回唯一值,但不承诺任务完成顺序
假设两个 goroutine 同时调用 Next,它们拿到 10 和 11 是确定的;但拿到 10 的任务可能因为网络请求更慢,最后才写入数据库。不要把序号当作时间戳,也不要用它推断日志先后。
| 需求 | Add(1) 能否满足 | 需要补充的设计 |
|---|---|---|
| 并发调用不重复 | 可以 | 所有领取都走同一个生成器实例 |
| 按完成时间连续 | 不可以 | 用队列、提交阶段或显式排序 |
| 服务重启后继续唯一 | 单靠内存不可以 | 持久化状态或外部发号服务 |
如果业务允许“领取即消耗”,某个任务失败后留下空洞是正常结果。若必须重试时复用同一个编号,就要把编号和任务状态一起持久化,而不是再次调用 Add(1)。

四个容易忽略的边界
- 不要复制:
atomic.Int64第一次使用后不能再复制。把生成器放进容器时保存指针,或在初始化完成后只移动未使用的值。 - 留意溢出:有符号整数到达上限后继续递增会回绕。序号接近上限时应切换区间或停止发号,不能把负数继续当作正 ID。
- 统一实例:每个请求临时创建一个生成器都会从 0 开始,得到的只是局部序号;共享生成器才能在同一进程内避免重复。
- 明确范围:atomic.Int64 只保护进程内共享内存。多副本服务要使用数据库自增列、Redis 原子命令或专门的 ID 服务。
常见问题
Add 返回的是旧值还是新值?
是新值。计数器为 4 时调用 Add(1),返回 5;如果需要旧值,要另行设计交换或减法逻辑,不能凭经验倒推。
用 atomic.Int64 就一定比互斥锁快吗?
不能这样保证。原子递增适合短小的共享状态更新,但完整业务仍可能受网络、分配和持久化影响;是否更快应以目标平台的基准测试为准。
序号必须严格按请求到达顺序怎么办?
不要只依赖 Add。先定义“到达”的判定点,再用串行队列或带确认的提交流程决定顺序;原子计数器只负责安全地领取一个数。
-
860 收藏
-
843 收藏
-
826 收藏
-
809 收藏
-
792 收藏
-
115 收藏
-
440 收藏
-
276 收藏
-
Golang · Go教程 | 44分钟前 | Context · 并发控制 · Go教程 · 请求生命周期 · Go Deadline context.Context 取消信号 context.WithoutCancel374 收藏
-
Golang · Go教程 | 56分钟前 | 并发 · Context · Go教程 · 取消信号 · Go context.AfterFunc context.AfterFunc stop Go 回调取消竞态 Go 判断回调是否开始117 收藏
-
153 收藏
-
353 收藏
-
500 收藏
-
Golang · Go教程 | 1小时前 | 性能优化 · 垃圾回收 · Go教程 · 标准库 sync · 对象池 · go bytes.Buffer Go sync.Pool Go 对象池 sync.Pool 缓存 Go 临时对象复用467 收藏
-
179 收藏
-
Golang · Go教程 | 2小时前 | Go教程 · 泛型函数 · 标准库 cmp · 配置默认值 · Go 类型语义 · Go 空字符串 Go cmp.Or Go 零值 Go 默认值 Go 显式零值452 收藏
-
187 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习