登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

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 用静态关系框图展示了 IDGeneratoratomic.Int64Add(1) 与返回序号之间的边界关系。

Go atomic.Int64 Add 生成序号的静态模块关系示意图
图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)

Go 原子序号与任务顺序持久化边界静态关系示意图
图2:结果示意图,展示原子计数、并发任务和持久化提交之间的静态边界;它不是实际运行截图。

四个容易忽略的边界

  • 不要复制:atomic.Int64 第一次使用后不能再复制。把生成器放进容器时保存指针,或在初始化完成后只移动未使用的值。
  • 留意溢出:有符号整数到达上限后继续递增会回绕。序号接近上限时应切换区间或停止发号,不能把负数继续当作正 ID。
  • 统一实例:每个请求临时创建一个生成器都会从 0 开始,得到的只是局部序号;共享生成器才能在同一进程内避免重复。
  • 明确范围:atomic.Int64 只保护进程内共享内存。多副本服务要使用数据库自增列、Redis 原子命令或专门的 ID 服务。

常见问题

Add 返回的是旧值还是新值?

是新值。计数器为 4 时调用 Add(1),返回 5;如果需要旧值,要另行设计交换或减法逻辑,不能凭经验倒推。

用 atomic.Int64 就一定比互斥锁快吗?

不能这样保证。原子递增适合短小的共享状态更新,但完整业务仍可能受网络、分配和持久化影响;是否更快应以目标平台的基准测试为准。

序号必须严格按请求到达顺序怎么办?

不要只依赖 Add。先定义“到达”的判定点,再用串行队列或带确认的提交流程决定顺序;原子计数器只负责安全地领取一个数。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>