当前位置:首页 > 文章列表 > Golang > Go教程 > Go sync/atomic 怎样用 atomic.Int64 记录有符号偏移量

Go sync/atomic 怎样用 atomic.Int64 记录有符号偏移量

来源:17golang原创 2026-10-18 05:50:52 0浏览 收藏

如果多个 goroutine 共同维护一个可能增加、也可能减少的偏移量,可以把字段声明为 atomic.Int64,用 Add 接收正负 int64,用 Load 读取当前快照。它的零值就是 0,不需要额外初始化;但这个类型一旦参与并发访问,就不能再复制,也不能把它当成相关业务状态的万能锁。

官方文档:https://pkg.go.dev/sync/atomic

要点速览
  • 有符号偏移量可以直接用 Add(delta) 表示前进和回退,正数与负数由调用方定义。
  • Load 得到的是这个整数在某一时刻的原子快照;Store 是无条件覆盖,Swap 还会返回旧值。
  • 需要“读取、判断、写回”保持为一次条件决策时,用 CompareAndSwap,不要把普通读取和赋值拼在一起。

先把偏移量定义成可正可负的 int64

例如一个分片消费器会因为确认消息向前移动,也会因为回放或纠偏向后移动。这个量不是“永远递增的计数器”,而是相对基准的偏移量,因此用 int64 更贴合模型:

package main

import (
    "fmt"
    "sync/atomic"
)

type Cursor struct {
    // offset 保存相对基准的有符号位置;零值表示没有偏移。
    offset atomic.Int64
}

func (c *Cursor) move(delta int64) int64 {
    // delta 可以为正,也可以为负;Add 返回更新后的值。
    return c.offset.Add(delta)
}

func (c *Cursor) current() int64 {
    // Load 只读取 offset,不会把 Cursor 的其他字段一起锁住。
    return c.offset.Load()
}

func main() {
    var c Cursor
    fmt.Println(c.move(120), c.move(-20), c.current())
}

这里的关键不是把负数“转换”为无符号数,而是让业务约定直接体现在 delta 上:向前为正,回退为负。多个 goroutine 同时调用 move 时,每次 Add 都是针对这个整数的原子加法,调用方拿到的是本次更新后的值。

Go atomic.Int64 通过 Add 和 Load 管理有符号偏移量的静态结构图
图1:有符号偏移量结构说明图,展示 Cursor、atomic.Int64、正负 delta 与 Load 快照的静态关系;这是原创说明图,不是运行截图。

用 Add 和 Load 管理并发增减与读取

在实际接口里,最好把原子字段藏在小对象后面,只暴露“移动”和“读取”两个语义清楚的方法。这样调用方不会误以为读取快照与其他字段天然一致:

type Offset struct {
    // value 只负责一个偏移量,相关元数据不要假定会被它一并保护。
    value atomic.Int64
}

func (o *Offset) Advance(n int64) int64 {
    // n 为负数时表示回退,仍由同一个原子加法完成。
    return o.value.Add(n)
}

func (o *Offset) Snapshot() int64 {
    // 读取快照不修改值;返回值可能在返回后立即过时。
    return o.value.Load()
}

Load 适合日志、指标或下一次计算的输入,但它不是事务快照。比如你先 Load 再修改另一个字段,期间其他 goroutine 可能已经改变了偏移量;如果业务要求两个字段同时变化,就要把整体状态放进更合适的同步边界。

重置和替换时区分 Store 与 Swap

偏移量需要回到某个基线时,可以使用 Store。如果重置动作还需要知道旧值,例如记录“从哪里切换到哪里”,则用 Swap 直接取得旧值:

func (o *Offset) Reset(base int64) int64 {
    // Store 无条件写入新基线,不关心旧值是什么。
    o.value.Store(base)
    return base
}

func (o *Offset) Replace(base int64) (old int64) {
    // Swap 原子地写入 base,并把替换前的值交给调用方。
    return o.value.Swap(base)
}

另外,atomic.Int64 的零值可以直接使用;它首次使用后不能复制。不要把含有已使用 atomic.Int64 的结构体按值传递、放进会产生副本的容器,或通过返回值复制它。共享对象通常应使用指针传递。

需要覆盖边界时用 CompareAndSwap

如果偏移量不能低于下界,单独的 Load 加 Add 不够:两个调用者都可能先看到同一个旧值。此时把“旧值仍然符合预期”和“写入候选值”交给 CompareAndSwap:

func (o *Offset) MoveIfAllowed(delta, min int64) (int64, bool) {
    for {
        old := o.value.Load()
        next := old + delta
        // 先拒绝越过业务下界的候选值,避免写入无效偏移。
        if next 

这个循环把条件判断和替换连接成一次原子决策:如果别的 goroutine 在中间改过值,CAS 会返回 false,代码重新读取最新值。这里的加法仍要遵守 int64 的业务范围,不应把溢出当作正常的偏移策略。

Go atomic.Int64 使用 CompareAndSwap 表达偏移量范围约束的静态结构图
图2:边界条件结构说明图,展示当前值、候选值、下界和 CompareAndSwap 之间的静态关系;这是原创说明图,不代表真实运行结果。

最后确认 atomic.Int64 的责任边界

atomic.Int64 只负责这个 int64 的原子读写与更新。它不会自动保护旁边的状态,也不会让“读取偏移、取出任务、更新任务状态”变成一个整体事务。需要多个值保持一致时,应重新设计临界区或把状态组合为一个可整体替换的对象。

需求调用语义
向前或回退Add(delta)原子加上有符号增量并返回新值
读取当前值Load()获得单个整数的原子快照
无条件重置Store(value)直接覆盖旧值
替换并记录旧值Swap(value)原子写入并返回旧值
满足条件才更新CompareAndSwap(old, new)旧值匹配时才替换

相关问题

atomic.Int64 的零值能直接使用吗?

可以。官方文档把零值定义为 0,声明一个未初始化的 atomic.Int64 就能调用 Add 或 Load。

为什么不能复制已经使用过的 atomic.Int64?

它内部维护着原子状态和不可复制标记;首次使用后复制会破坏调用方对同一共享状态的理解,因此应通过指针共享外层对象。

Add 能不能防止偏移量超过业务范围?

不能。Add 只负责原子加法;范围校验需要使用 CompareAndSwap 之类的条件更新,或者在更大的同步边界内完成。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go sync/atomic 怎样原子更新指针指向的不可变配置Go sync/atomic 怎样原子更新指针指向的不可变配置
上一篇
Go sync/atomic 怎样原子更新指针指向的不可变配置
Go slices 怎样用 BinarySearch 查找有序切片
下一篇
Go slices 怎样用 BinarySearch 查找有序切片
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    513次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    572次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    598次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    549次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    366次使用