Golang:精通sync/atomic 包的Atomic 操作
在本指南中,我们将探索sync/atomic包的细节,展示如何编写更安全、更高效的并发代码。无论你是经验丰富的Gopher还是刚刚起步,你都会发现有价值的见解来提升Go编程技能。让我们一起开启原子运算的力量吧!
理解Go中的原子操作
在快节奏的并发编程世界中,原子操作是线程安全的哨兵。但是Go中的原子操作到底是什么,为什么要关心呢?让我们开始吧!

原子操作是不可分割的操作,对系统的其余部分来说似乎是瞬间发生的。在Go语言中,sync/atomic包提供这些操作,确保对共享变量的复杂操作不会中断。这在并发编程中是至关重要的,因为多个程序可能同时访问相同的数据。
考虑一下:你正在构建一个高流量的web应用程序,并且您需要跟踪活跃用户的数量。如果没有原子操作,您可能会遇到经典的竞争条件:
var activeUsers intfunc incrementUsers() {activeUsers++ // This is not atomic!
}
在并发环境中,这种看似无害的增量可能导致数据竞争和错误计数。下面示例采用原子操作:
import "sync/atomic"var activeUsers int64func incrementUsers() {atomic.AddInt64(&activeUsers, 1) // Atomic and safe!
}
现在,无论有多少例程调用incrementUsers(),计数总是准确的。
但是为什么要使用原子操作而不是互斥锁或其他同步方法呢?这一切都与性能有关。与基于锁的操作相比,原子操作快如闪电。事实上,Go团队的一项研究表明,对于简单的操作,原子可以比互斥体快3倍!
下面是一个简短的对比:
| Operation | Atomic | Mutex |
|---|---|---|
| Read | 2 ns | 6 ns |
| Write | 3 ns | 8 ns |
| CAS | 4 ns | 10 ns |
(注:这些是近似值,可能会因硬件和Go版本而异)
原子操作适用于需要快速、简单同步的场景。它们适合:
-
计数器(像我们的活动用户示例)
-
标志(例如,检查进程是否完成)
-
简单共享状态管理
然而,需要注意的是,原子操作并不是万能的。对于复杂的数据结构或需要自动执行多个相关操作时,互斥锁或其他同步原语可能更合适。当我们深入研究同步/原子包时,请记住:能力越大责任越大。明智地使用原子操作,您的并发Go程序将以改进的性能和可靠性感谢您。
探索sync/atomic包
sync/atomic包是Go中并发编程工具的宝库。它就像一把瑞士军刀,可以安全有效地处理共享变量。让我们打开这个强大的包,看看它有什么提供!

sync/atomic的核心是提供低级原子内存原语,这些原语是同步算法的构建块。这些原语是用汇编语言实现的,以获得最大的效率,使它们非常快。
主要类型和功能
该包主要处理这些类型:
int32,int64uint32,uint64uintptrunsafe.Pointer
对于每种类型,sync/atomic都提供了一组函数:
-
Load:自动加载并返回变量的值。
-
Store:自动地将值存储到变量中。
-
Add:自动地向变量添加一个值并返回新值。
-
Swap:自动地将一个值与一个变量交换,并返回旧值。
-
CompareAndSwap:自动比较变量与旧值,如果它们相等,则将其与新值交换。
让我们看看这些操作:
import ("fmt""sync/atomic"
)func main() {var counter int64// Storeatomic.StoreInt64(&counter, 42)// Loadvalue := atomic.LoadInt64(&counter)fmt.Println("Counter value:", value)// AddnewValue := atomic.AddInt64(&counter, 10)fmt.Println("New counter value:", newValue)// SwapoldValue := atomic.SwapInt64(&counter, 100)fmt.Println("Old value:", oldValue, "New value:", atomic.LoadInt64(&counter))// CompareAndSwapswapped := atomic.CompareAndSwapInt64(&counter, 100, 200)fmt.Println("Swapped:", swapped, "Current value:", atomic.LoadInt64(&counter))
}
输出如下:
Counter value: 42
New counter value: 52
Old value: 52 New value: 100
Swapped: true Current value: 200
原子值操作
除了这些基本操作之外,sync/atomic还提供了Value类型,用于自动存储和加载任意值:
type Config struct {Threshold intName string
}func main() {var configValue atomic.Value// Store a ConfigconfigValue.Store(Config{Threshold: 10, Name: "Default"})// Load the Configconfig := configValue.Load().(Config)fmt.Printf("Config: %+v\n", config)
}
这对于在并发环境中安全地更新配置值非常有用。
原子指针操作
对于更高级的用例,sync/atomic提供了指针上的原子操作:
type User struct {Name stringAge int
}func main() {var userPtr atomic.Pointer[User]// Store a UseruserPtr.Store(&User{Name: "Alice", Age: 30})// Load the Useruser := userPtr.Load()fmt.Printf("User: %+v\n", *user)
}
这允许对复杂数据结构进行原子操作,从而启用无锁算法和数据结构。

性能考虑
虽然原子操作很快,但它们不是免费的。下面是一个比较原子操作和常规操作的快速基准测试:
func BenchmarkRegularIncrement(b *testing.B) {var x int64for i := 0; i < b.N; i++ {x++}
}func BenchmarkAtomicIncrement(b *testing.B) {var x int64for i := 0; i < b.N; i++ {atomic.AddInt64(&x, 1)}
}
运行这个基准测试可能会产生如下结果:
BenchmarkRegularIncrement-8 1000000000 0.3 ns/op
BenchmarkAtomicIncrement-8 100000000 12 ns/op
正如您所看到的,原子操作比常规操作慢。但是,在需要同步的并发场景中,它们通常比使用mutexes更快。
sync/atomic包是Go的并发工具包中的一个强大工具。通过理解它的功能并明智地使用它,您可以编写高效、无竞争的并发代码。
记住,能力越大责任越大。明智地使用原子操作,你的Go程序将会因为性能和可靠性的提高而感谢你
实现原子计数器
原子计数器是并发编程中的基本构建块,它允许多个例程安全地增加或减少共享值。让我们探索一下如何使用sync/atomic包来实现它们。
- 创建和初始化原子计数器
在Go中,我们通常使用int64作为原子计数器。下面是如何创建和初始化一个:
import ("sync/atomic"
)var counter int64 // Initialized to 0 by default// Or, if you want to start with a non-zero value:
counter := atomic.Int64{}
counter.Store(100)
- 递增和递减计数器
要自动修改计数器,可以使用AddInt64函数:
// Increment
atomic.AddInt64(&counter, 1)// Decrement
atomic.AddInt64(&counter, -1)// Add or subtract any value
atomic.AddInt64(&counter, 10)
atomic.AddInt64(&counter, -5)
- 读计数器值
要读取计数器的当前值,使用LoadInt64:
currentValue := atomic.LoadInt64(&counter)
fmt.Printf("Current counter value: %d\n", currentValue)
- 使用原子计数器的最佳实践
-
使用正确的类型:对于原子计数器始终使用int64,以确保所有体系结构上的64位对齐。
-
避免混合访问:不要在同一个变量上混合使用原子操作和非原子操作。
-
考虑溢出:记住原子计数器可以溢出,就像常规整数一样。
-
正确使用指针:始终向原子函数传递指针。
下面是演示这些实践的完整示例:
package mainimport ("fmt""sync""sync/atomic"
)func main() {var counter int64var wg sync.WaitGroupfor i := 0; i < 1000; i++ {wg.Add(1)go func() {atomic.AddInt64(&counter, 1)wg.Done()}()}wg.Wait()fmt.Printf("Final counter value: %d\n", atomic.LoadInt64(&counter))
}
这个程序创建了1000个例程,每个例程增加一次计数器。由于原子操作,最终值将始终为1000。
处理原子布尔值
原子布尔值对于在并发程序中实现标志非常有用。虽然Go没有专用的原子布尔类型,但我们可以使用uint32来原子地表示布尔值。
- 设置和获取原子布尔值
下面是如何处理原子布尔值:
import ("sync/atomic"
)var flag uint32// Set the flag to true
atomic.StoreUint32(&flag, 1)// Set the flag to false
atomic.StoreUint32(&flag, 0)// Check if the flag is set
if atomic.LoadUint32(&flag) == 1 {fmt.Println("Flag is set!")
} else {fmt.Println("Flag is not set.")
}
- 在并发程序中实现标志
原子布尔值非常适合实现控制多个线程行为的标志。下面是带有停止标志的简单worker池的例子:
package mainimport ("fmt""sync""sync/atomic""time"
)func main() {var stopFlag uint32var wg sync.WaitGroup// Start 5 workersfor i := 0; i < 5; i++ {wg.Add(1)go worker(i, &stopFlag, &wg)}// Let workers run for 2 secondstime.Sleep(2 * time.Second)// Signal workers to stopatomic.StoreUint32(&stopFlag, 1)wg.Wait()fmt.Println("All workers stopped")
}func worker(id int, stopFlag *uint32, wg *sync.WaitGroup) {defer wg.Done()for {if atomic.LoadUint32(stopFlag) == 1 {fmt.Printf("Worker %d stopping\n", id)return}// Simulate some worktime.Sleep(200 * time.Millisecond)fmt.Printf("Worker %d working\n", id)}
}
- 原子布尔变量与常规布尔变量
对布尔变量使用原子操作比常规布尔变量有一些优势:
-
线程安全:原子布尔值在并发环境中使用是安全的,不需要额外的同步。
-
性能优势:对于简单的标志,原子布尔值通常比使用互斥锁更快。
-
可见性保证:原子操作确保所有例程都能立即看到更改。
然而,也有取舍:
-
内存使用:原子布尔值使用32位而不是常规布尔值的1位。
-
复杂性:语法稍微复杂一些
原子交换和比较交换(CAS)
原子交换和比较与交换(CAS)操作是高级原子原语,构成了许多无锁算法的主干。这些操作允许进行比简单的加载和存储更复杂的原子更新,从而实现各种并发数据结构和算法的高效和线程安全的实现。
- 实现原子交换操作
Swap操作自动地将变量的值与新值交换,并返回旧值。这在需要更新值的同时还需要知道其先前状态的情况下非常有用。
下面是如何使用Swap操作:
package mainimport ("fmt""sync/atomic"
)func main() {var value int64 = 100// Atomically swap the value and get the old valueoldValue := atomic.SwapInt64(&value, 200)fmt.Printf("Old value: %d, New value: %d\n", oldValue, atomic.LoadInt64(&value))
}
输出:
Old value: 100, New value: 200
交换操作可用于各种类型:
SwapInt32,SwapInt64SwapUint32,SwapUint64SwapUintptrSwapPointer
理解比较与交换(CAS)
比较与交换(CAS)是一种更强大的原语,它支持仅在值与预期值匹配时更新值。这个操作是许多无锁算法的基础。
以下是CAS的基本思想:
-
读取变量的当前值。
-
基于该值执行计算。
-
更新变量,但前提是它仍然具有您最初读取的值。
如果该值在步骤1和步骤3之间发生了更改,则CAS操作失败,通常需要重试该操作。
这里有一个简单的例子:
package mainimport ("fmt""sync/atomic"
)func main() {var value int64 = 100// Try to update value from 100 to 200swapped := atomic.CompareAndSwapInt64(&value, 100, 200)fmt.Printf("CAS successful: %v, New value: %d\n", swapped, atomic.LoadInt64(&value))// Try again, but it will fail because value is no longer 100swapped = atomic.CompareAndSwapInt64(&value, 100, 300)fmt.Printf("CAS successful: %v, New value: %d\n", swapped, atomic.LoadInt64(&value))
}
输出结果:
CAS successful: true, New value: 200
CAS successful: false, New value: 200
总结
我们已经游历了Go的sync/atomic包的迷人世界,揭示了原子操作的强大和巧妙。从掌握原子计数器到使用Compare-and-Swap实现无锁算法,您现在可以编写更高效、更安全的并发围棋程序了。请记住,虽然原子操作可以使代码负担过重,但它们需要仔细考虑并正确实现。
相关文章:
Golang:精通sync/atomic 包的Atomic 操作
在本指南中,我们将探索sync/atomic包的细节,展示如何编写更安全、更高效的并发代码。无论你是经验丰富的Gopher还是刚刚起步,你都会发现有价值的见解来提升Go编程技能。让我们一起开启原子运算的力量吧! 理解Go中的原子操作 在快…...
微信小程序如何使用decimal计算金额
第三方库地址:GitHub - MikeMcl/decimal.js: An arbitrary-precision Decimal type for JavaScript 之前都是api接口走后端计算,偶尔发现这个库也不错,计算简单,目前发现比较准确 上代码 导入js import Decimal from ../../uti…...
最新Modular公司之MAX和Mojo作者 克里斯·拉特纳简介
Chris Lattner(克里斯拉特纳) 是一位著名的计算机科学家和软件工程师,以其在编程语言、编译器技术和软件开发工具领域的贡献而闻名。以下是关于他的详细介绍: 1. 主要成就 (1)LLVM 项目的创始人 Chris La…...
Redis数据库篇 -- Pipeline
一. 什么是Pipeline 在传统的请求-响应模式中,客户端与服务器之间的通信流程如下: 客户端发送一个命令到服务器。服务器接收命令并执行。服务器将执行结果返回给客户端。客户端接收结果后,发送下一个命令 在这种传统的模式下,…...
爬虫自动化(DrissionPage)
目录 ?一.介绍: 下载DrissionPage,还是我们熟悉的pip: 环境准备: ?二.基本代码: 它对于的导包和类使用: 窗口的设置: 和获取的页面的滑动: 3.进一步认识DrissionPage: 浏览器可以多开…...
常见string库中的函数(C语言超详细)
文章目录 strcspnstrcpystrncpystrcatstrncatstrcmpstrncmpstrchrstrrchrstrstrstrtokstrlenstrnlen strcspn 原型: size_t strcspn(const char *str1, const char *str2);功能: strcspn 会扫描 str1,并返回一个整数,表示 str1 中第一个匹配…...
单例模式几种实现
静态内部类holder实现(推荐) public class UniqueIdGenerator {public static final UniqueIdGenerator INSTANCE Holder.INSTANCE;// Private holder class for lazy initializationprivate static class Holder {static final UniqueIdGenerator INS…...
android中关于CheckBox自定义选中图片选中无效问题
在android xml 布局中,使用CheckBox控件设置选中背景图代码如下 <CheckBoxandroid:layout_width"wrap_content"android:layout_height"wrap_content"android:button"drawable/dfrd_common_selecotr_check"android:paddingStart&q…...
虚拟局域网之详解(Detailed Explanation of Virtual Local Area Network)
虚拟局域网之详解 VLAN (virtual localArea network)是一种虚拟局域网技术,它可以将一个物理局域网划分为多个逻辑上的虚拟局域网。 基于交换式以太网的虚拟局域网在交换式以太网中,利用VLAN技术,可以将由交换机连接成的物理网络划分成多个…...
双亲委派(JVM)
1.双亲委派 在 Java 中,双薪委派通常是指双亲委派模型,它是 Java 类加载器的一种工作模式,用于确保类加载的安全性和一致性。以下是其相关介绍: 定义与作用 定义:双亲委派模型要求除了顶层的启动类加载器外…...
第二十一章:考研的艰难抉择与放弃入学的转折
深秋时节,校园宛如被大自然精心雕琢的艺术殿堂。金黄的银杏叶在阳光的轻抚下,闪烁着细碎的光芒,微风拂过,叶片相互摩挲,发出沙沙的轻响,仿佛在低声诉说着岁月的故事。一片片银杏叶悠悠然飘落,宛…...
webpack配置之---output.chunkLoading
output.chunkLoading webpack.output.chunkLoading 配置项用于指定 Webpack 如何加载异步 chunk(即按需加载的代码块)。在现代 Webpack 版本中,异步代码分割成为了非常常见的功能,chunkLoading 配置项就用于控制 Webpack 加载这些…...
升级RAG应用程序与Redis向量库
Redis Vector Library (RedisVL) 简化AI应用开发 几个月前,Redis推出了Redis向量库(RedisVL),以简化人工智能(AI)应用的开发。自那时起,我们引入了强大的新功能,支持大规模的语言模…...
【starrocks学习】之将starrocks表同步到hive
目录 方法 1:通过HDFS导出数据 1. 将StarRocks表数据导出到HDFS 2. 在Hive中创建外部表 3. 验证数据 方法 2:使用Apache Spark同步 1. 添加StarRocks和Hive的依赖 2. 使用Spark读取StarRocks数据并写入Hive 3. 验证数据 方法 3:通过…...
HTML应用指南:利用GET请求获取全国盒马门店位置信息
随着新零售业态的发展,门店位置信息的获取变得至关重要。作为新零售领域的先锋,盒马鲜生不仅在商业模式创新上持续领先,还积极构建广泛的门店网络,以支持其不断增长的用户群体。本篇文章,我们将继续探究GET请求的实际应用,我们使用Python的requests库通过GET请求,从盒马…...
openEuler部署 sysstat工具
查看环境 [rootlocalhost lxm]# cat /etc/os-release NAME"openEuler" VERSION"23.09" ID"openEuler" VERSION_ID"23.09" PRETTY_NAME"openEuler 23.09" ANSI_COLOR"0;31"查看 yum 源 [rootlocalhost lxm]# he…...
使用 Three.js 实现炫酷的除夕烟花特效
1,前言 在除夕夜,璀璨的烟花点亮夜空,为节日增添了浓厚的喜庆氛围。在 Web 端,我们可以使用 Three.js 来模拟这种美轮美奂的烟花特效,让网页也能展现绚丽的节日气息。本文将介绍如何利用 Three.js 及其着色器技术&…...
LMM-3DP:集成 LMM 规划器和 3D 技能策略实现可泛化操作
25年1月来自UCSD的论文“Integrating LMM Planners and 3D Skill Policies for Generalizable Manipulation”。 大型多模态模型 (LMM) 的视觉推理能力和 3D 特征场语义丰富性的最新进展,拓展了机器人能力的范围。这些发展对于弥合 LMM 高级推理与利用 3D 特征场低…...
Linux——基础命令3
1、关机重启命令 reboot指令 作用:重启计算机 语法:reboot shutdown指令 作用:关机 语法:shutdown -h 时间 时间常见的值: now (立即关机) m(m表示minutes数字)eg&…...
ChatGPT提问技巧:行业热门应用提示词案例-文案写作
ChatGPT 作为强大的 AI 语言模型,已经成为文案写作的得力助手。但要让它写出真正符合你需求的文案,关键在于如何与它“沟通”,也就是如何设计提示词(Prompt)。以下是一些实用的提示词案例,帮助你解锁 ChatG…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
