Golang原子操作有哪些?sync/atomic包全解析
在Golang并发编程中,`sync/atomic`包提供了一组高效的原子操作,利用CPU指令直接操作整数和指针类型,实现无锁并发安全。相比传统的`sync.Mutex`锁,`sync/atomic`适用于计数器、标志位、配置更新等简单场景,性能更优。本文将深入解析`sync/atomic`包中的`Add`、`CompareAndSwap`、`Load`、`Store`、`Swap`以及`Value`类型等核心原子操作,并通过实例演示如何在实际项目中应用。同时,本文还将对比`sync/atomic`与`sync.Mutex`的适用场景,帮助开发者在操作复杂度与性能需求之间做出明智选择,从而编写出更高效、更安全的并发程序。
sync/atomic通过CPU指令提供整数和指针类型的原子操作,如Add、CompareAndSwap、Load、Store、Swap及Value类型,实现无锁并发安全,适用于计数器、标志位、配置更新等简单场景,性能优于sync.Mutex;而sync.Mutex适用于保护复杂数据结构或临界区含耗时操作的场景,两者选择需权衡操作复杂度与性能需求。

Golang的sync/atomic包提供了一系列底层的、由CPU指令支持的原子操作,主要针对基本数据类型,如整数和指针。这些操作允许在不使用传统互斥锁(sync.Mutex)的情况下,对共享变量进行并发安全的读写、增减或比较并交换,从而有效减少锁竞争,提升高并发场景下的性能。
解决方案
sync/atomic包提供了以下主要的原子操作,它们都以atomic.为前缀:
AddInt32/AddInt64/AddUint32/AddUint64: 原子性地将一个值加到现有变量上,并返回新值。var counter int64 atomic.AddInt64(&counter, 1) // counter现在是1
CompareAndSwapInt32/CompareAndSwapInt64/CompareAndSwapUint32/CompareAndSwapUint64/CompareAndSwapPointer: 比较并交换(CAS)操作。如果变量的当前值等于期望值,则原子性地将其更新为新值,并返回true;否则不进行任何操作并返回false。这是许多无锁算法的基石。var value int32 = 10 // 如果value当前是10,就把它设置为20 swapped := atomic.CompareAndSwapInt32(&value, 10, 20) // swapped为true, value为20 // 如果value当前是10(现在是20了),就把它设置为30 swapped = atomic.CompareAndSwapInt32(&value, 10, 30) // swapped为false, value仍为20
LoadInt32/LoadInt64/LoadUint32/LoadUint64/LoadPointer/LoadValue: 原子性地读取变量的当前值。这确保了即使在并发写入时,也能获取到最新的、完整的变量状态,避免了部分写入的问题。var config atomic.Value config.Store("initial_config") loadedConfig := config.Load().(string) // loadedConfig为"initial_config"StoreInt32/StoreInt64/StoreUint32/StoreUint64/StorePointer/StoreValue: 原子性地将一个新值写入变量。与Load类似,它确保写入操作的完整性。var status int32 atomic.StoreInt32(&status, 1) // status现在是1
SwapInt32/SwapInt64/SwapUint32/SwapUint64/SwapPointer: 原子性地将变量设置为新值,并返回变量的旧值。var oldVal int64 = 5 // 将oldVal设置为10,并返回它原来的值5 previous := atomic.SwapInt64(&oldVal, 10) // previous为5, oldVal为10
Value类型:atomic.Value是一个特殊的原子操作,它可以存储任意类型的接口值,并提供Load()和Store()方法进行原子性的读写。它常用于存储配置或不可变的数据结构,避免在更新时锁定整个结构。
为什么选择sync/atomic而非sync.Mutex来处理并发?
这问题问得好,因为很多时候我们写并发代码,第一反应就是上锁。但实际上,sync/atomic和sync.Mutex解决的是不同粒度、不同场景下的并发问题。
sync.Mutex是一种悲观锁,它假设多个协程会同时访问共享资源并产生冲突,因此在访问前就将资源锁住,确保同一时间只有一个协程能操作。这就像你进图书馆借书,为了避免别人拿走你要的书,你直接把整个书架都锁起来,等你自己拿完再解锁。这种方式简单、安全,适用于保护复杂的共享数据结构,或者涉及多个操作步骤需要原子性完成的场景。
而sync/atomic则是一种乐观锁的思路。它不锁住资源,而是尝试直接对共享变量进行操作。如果操作成功(即在操作期间没有其他协程修改该变量),那就完成了;如果失败(说明有其他协程抢先修改了),它会重试。这就像你借书,你直接去拿,如果书还在就拿走,如果不在了就再去看看有没有别的书。这种方式通常由CPU的特定指令(如CAS指令)支持,避免了操作系统层面的上下文切换,因此对于简单的数值或指针操作,它的性能开销远低于互斥锁。
我个人经验来看,当你只是想对一个计数器进行增减,或者更新一个配置指针,却用了sync.Mutex,那往往是“杀鸡用牛刀”了。互斥锁引入的开销,尤其是在高并发竞争激烈时,会非常显著。sync/atomic在这些场景下,不仅能提供更好的性能,代码也可能更简洁。但反过来,如果你的操作涉及多个字段的更新,或者需要维护复杂的数据结构一致性,那sync.Mutex的安全性就显得不可替代了。选择哪个,真的要看具体需求和场景。
CompareAndSwap(CAS)操作在sync/atomic中扮演什么核心角色?
CompareAndSwap(CAS),或者说比较并交换,绝对是sync/atomic包乃至整个现代并发编程中的一个核心概念。它不是简单地设置一个值,而是先检查一个条件(当前值是否等于期望值),如果条件满足,才执行更新操作。这个“检查-更新”的整个过程是原子性的,不可中断。
你可以把它想象成一个守门员,你告诉他:“如果现在是A状态,就把门打开到B状态。”守门员会迅速检查,如果确实是A,他会立即打开到B,并告诉你“搞定!”。如果他发现已经不是A了(比如别人在他检查前已经改成了C),他会告诉你“不行,状态不对!”。
CAS的强大之处在于,它允许我们构建无锁(lock-free)或免锁(wait-free)的数据结构和算法。传统的锁机制,比如sync.Mutex,在竞争激烈时,会频繁地导致协程阻塞和唤醒,带来上下文切换的开销。而基于CAS的算法,协程在发现冲突时,不会被阻塞,而是选择重试,这在某些场景下能提供更高的吞吐量和更低的延迟。
举个例子,一个简单的原子计数器就可以用CAS来实现,尽管atomic.AddInt64更直接。但更复杂的,比如实现一个无锁的栈(stack)或队列(queue),CAS就是不可或缺的。你需要用CAS来更新栈顶指针或队列的头尾指针,确保在多协程同时出入栈/队时,数据结构依然保持一致性。
当然,CAS也不是万能药。它可能会导致“ABA”问题(如果一个值从A变为B,又变回A,CAS会误以为没有发生变化),虽然在Go的标准库中,对于基本类型这通常不是大问题,但在构建复杂无锁结构时需要考虑。此外,频繁的CAS失败重试也可能导致“忙等”(busy-waiting),消耗CPU资源。但无论如何,理解CAS是深入理解现代并发编程的关键一步。
在实际项目中,何时优先考虑使用sync/atomic而不是sync.Mutex?
在实际开发中,选择sync/atomic还是sync.Mutex,这其实是一个权衡和取舍的问题,没有绝对的答案,但有一些清晰的指导原则。
我通常会优先考虑sync/atomic的场景:
- 简单的计数器或标志位:这是最典型的应用场景。比如统计请求数量、并发任务数、或者一个布尔型的开关状态。使用
atomic.AddInt64或atomic.StoreInt32来更新,比加锁解锁效率高得多。var requestCount int64 // 每次处理请求 atomic.AddInt64(&requestCount, 1)
- 单个指针的原子性更新:当需要原子性地更换一个指向不可变数据结构的指针时,
atomic.Pointer或atomic.Value非常有用。例如,更新一个全局配置对象,你可以创建一个新的配置对象,然后原子性地替换旧的指针,这样读取方总能看到一个完整的、一致的配置版本,而不需要加锁。type Config struct { // ... 配置字段 } var currentConfig atomic.Value // 存储 *Config // 初始化 currentConfig.Store(&Config{/* ... */}) // 更新配置 newConfig := &Config{/* ... */} currentConfig.Store(newConfig) // 读取配置 cfg := currentConfig.Load().(*Config) - 性能敏感的临界区:如果你的代码段非常短,只涉及对一个基本类型变量的读写,并且这个代码段是系统性能瓶颈之一,那么
sync/atomic可以显著降低开销。比如,在一个高并发的缓存系统中,更新缓存命中率的统计。 - 构建无锁数据结构的基础:如果你正在尝试实现一些高级的无锁数据结构(如无锁队列、无锁哈希表),那么CAS操作(
CompareAndSwap)是构建这些结构的基础。但这通常是高级且复杂的任务,需要对并发模型有深刻理解。
反之,我会倾向于使用sync.Mutex的场景:
- 复杂数据结构的操作:当你需要保护
map、slice、包含多个字段的结构体,或者需要执行一系列操作(比如先检查、再修改、再记录日志)作为一个整体原子性完成时,sync.Mutex是更安全、更易于理解和维护的选择。atomic操作只能针对单个变量。 - 临界区包含I/O或耗时操作:如果你的临界区中包含了文件I/O、网络请求或者其他可能长时间阻塞的操作,那么使用
sync.Mutex是合适的。因为这些操作的耗时远超锁本身的开销,而且atomic的忙等重试在这种情况下会浪费大量CPU周期。 - 代码可读性和维护性优先:对于大多数业务场景,
sync.Mutex的代码逻辑更直观,出错的概率也相对较低。atomic虽然性能好,但它的语义更底层,如果使用不当,很容易引入难以调试的并发bug。
总结来说,sync/atomic是针对简单、单值操作的“快车道”,适用于对性能有极致要求的特定场景。而sync.Mutex则是“安全通道”,适用于大多数需要保证数据一致性的复杂场景。在做选择时,我的建议是:先考虑sync.Mutex,如果分析后发现它确实是性能瓶颈,并且操作足够简单,才转向sync/atomic。 过早优化往往是万恶之源。
终于介绍完啦!小伙伴们,这篇关于《Golang原子操作有哪些?sync/atomic包全解析》的介绍应该让你收获多多了吧!欢迎大家收藏或分享给更多需要学习的朋友吧~golang学习网公众号也会发布Golang相关知识,快来关注吧!
Excel自动保存设置教程
- 上一篇
- Excel自动保存设置教程
- 下一篇
- MySQL数据备份方法与工具推荐
-
- Golang · Go教程 | 15分钟前 | 单元测试 · 错误处理 · Go教程 · errors.Join · errors.Is · errors.Is Go错误处理 Go教程 errors.Join 多错误返回 批量校验
- Go errors.Join 怎么用:多错误返回、errors.Is 判断和 nil 兼容
- 352浏览 收藏
-
- Golang · Go教程 | 38分钟前 | Context · 超时控制 · Go教程 · http.Client · Transport · Go context 请求超时 Transport http.Client Client.Timeout ResponseHeaderTimeout
- Go HTTP 客户端超时怎么设:Client.Timeout、context 和 Transport 分层预算
- 218浏览 收藏
-
- Golang · Go教程 | 2小时前 | CI/CD · gitHub actions · Go教程 · 自托管 Runner · 持续集成 · Go 持续集成 CI Go test GitHub Actions self-hosted runner 自托管 runner
- Go 项目用 GitHub Actions 自托管 runner:版本强制执行前该怎么整理 CI
- 340浏览 收藏
-
- Golang · Go教程 | 10小时前 | HTTP · 文件下载 · Go教程 · Range请求 · ServeContent · 断点续传 Content-Range Go教程 HTTP Range ServeContent 206 Partial Content 视频拖动
- Go 实现 HTTP Range 下载:用 ServeContent 支持断点续传和视频拖动
- 250浏览 收藏
-
- Golang · Go教程 | 21小时前 | HTTP服务 · Go教程 · 后端开发 · 超时配置 · 服务稳定性 · net/http WriteTimeout HTTP超时 Go教程 ReadHeaderTimeout IdleTimeout
- Go HTTP 服务超时怎么配:ReadHeaderTimeout、WriteTimeout 和 IdleTimeout 实战
- 140浏览 收藏
-
- Golang · Go教程 | 1天前 | 错误处理 · Context · 并发控制 · Go教程 · 并发控制 Go教程 context取消 context.WithCancelCause context.Cause
- Go context.WithCancelCause 怎么用:把取消原因带回请求链路
- 342浏览 收藏
-
- Golang · Go教程 | 1天前 | HTTP · Go教程 · 问题排查 · io.ReadAll · JSON绑定 · Go教程 Go HTTP请求体 io.ReadAll Request Body JSON绑定
- Go HTTP 请求体为什么只能读一次:io.ReadAll 后绑定参数为空怎么排查
- 244浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ljg-skills
- ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
- 4394次使用
-
- MELO音乐
- MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
- 4064次使用
-
- UniScribe
- UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
- 4045次使用
-
- 剧云
- 剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
- 4229次使用
-
- 万象有声
- 万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
- 4201次使用
-
- Java 性能优化上线清单:从定位、改造到灰度发布
- 2026-06-11 860浏览
-
- Spring Boot 压测验证:Gatling、JMeter 与性能回归门禁
- 2026-06-11 843浏览
-
- Java NMT 非堆内存排查:Direct Buffer、线程栈与 Metaspace 分析
- 2026-06-11 826浏览
-
- Spring Boot 容器内存优化:JVM 堆、非堆与 MaxRAMPercentage
- 2026-06-11 809浏览
-
- Tomcat 连接与线程参数调优:maxThreads、acceptCount 与 KeepAlive
- 2026-06-11 792浏览

