Go语言的原子操作
当我们想要对某个变量并发安全的修改,除了使用官方提供的mutex,还可以使用sync/atomic包的原子操作,它能够保证对变量的读取或修改期间不被其他的协程所影响。
Golang提供的原子操作都是非侵入式的,由标准库sync/atmoic包提供,直接由底层CPU硬件支持。
也就是在硬件层次去实现的,性能较好,不需要像mutex那样记录很多状态。当然,mutex不止是对变量的并发控制,更多的是对代码块的并发控制,两者侧重点不一样。
一.介绍
原子操作即是进行过程中不能被中断的操作,针对某个值的原子操作在被进行的过程中,CPU绝不会再去进行其他的针对该值的操作。
具体的原子操作在不同的操作系统中实现是不同的。比如在Intel的CPU架构机器上,主要是使用总线锁的方式实现的。大致的意思就是当一个CPU需要操作一个内存块的时候,向总线发送一个LOCK信号,所有CPU收到这个信号后就不对这个内存块进行操作了。等待操作的CPU执行完操作后,发送UNLOCK信号,才结束。在AMD的CPU架构机器上就是使用MESI一致性协议的方式来保证原子操作。所以我们在看atomic源码的时候,我们看到它针对不同的操作系统有不同汇编语言文件。
Golang在sync包中已经提供了锁,为什么还需要使用atomic原子操作呢?
1)加锁的代价比较高,耗时多,需要上下文切换。
2)原子操作只针对基本数据类型,不支持结构体、自定义数据类型
3)原子操作在用户态可以完成,性能比互斥锁要高。
4)针对特定需求原子操作步骤简单,无需加锁解锁步骤。
为什么 atomic 比mutex快?
1)原子操作很快,因为它们依赖于CPU指令而不是依赖外部锁。使用互斥锁时,每次获得锁时,goroutine都会短暂暂停或中断,这种阻塞占使用互斥锁所花费时间的很大一部分(他们是由操作系统调度的)。原子操作可以在没有任何中断的情况下执行。
2)原子操作是能够保证执行期间是连续且不会被中断的,临界区只能保证访问共享数据是按顺序访问的,但并不能保证访问期间不会被切换上下文。
CAS
CAS是CPU硬件同步原语,是Compare And Swap的缩写
Go中的CAS操作,是借用了CPU提供的原子性指令来实现。CAS操作修改共享变量时候不需要对共享变量加锁,
而是通过类似乐观锁的方式进行检查,本质还是不断的占用CPU资源换取加锁带来的开销(比如上下文切换开销)
原子操作中的CAS,在sync/atomic包中,这类原子操作由名称以CompareAndSwap为前缀的若干个函数提供
优势:
- 可以在不形成临界区和创建互斥量的情况下完成并发安全的值替换操作。这可以大大的减少同步对程序性育
劣势:
- 在被操作值被频繁变更的情况下,CAS操作并不那么容易成功。因为需要对oLd值进行匹配,只有匹配成功
当前atomic 包有以下几种原子操作:Add、CompareAndSwap、Load、Store、Swap
二.操作
Add,CompareAndSwap,Load,Store,Swap
2.1 Add (增或减)
- 用于处理增加和减少的原子操作,函数名以Add为前缀,后跟针对特定类型的名称。
- 原子增被操作的只能是数值类型,即int32,int64,uint32,uint64,uinptr。
- 原子增减函数的第一个参数为原值,第二个是要增多少。
- 方法:
func AddInt32(addr *int32, delta int32) (new int32)
func AddUint32(addr *uint32, delta uint32) (new uint32)
func AddInt64(addr *int64, delta int64) (new int64)
func AddUint64(addr *uint64, delta uint64) (new uint64)
func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)
package mainimport ("fmt""sync/atomic"
)func main() {var num int32fmt.Println("num is ", num)atomic.AddInt32(&num, 10)fmt.Println("new num is ", num)
}
减法就很简单,就直接负值即可,就可以实现。
2.2 CompareAndSwap (比较并交换)
什么是比较并交换?
我们来看它的参数,有一个old和new值,分别表示它的原始值和新值,如果说这个原始值输入不对,则不会改变这个new值。
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)
func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)
func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
package mainimport ("fmt""sync/atomic"
)func main() {var num int32fmt.Println("num is ", num)atomic.CompareAndSwapInt32(&num, 0, 10)fmt.Println("new num is ", num)
}
可以试试,把old值不设置为0,它并不报错,只是不改变值而已
2.3 Load (读取)
Load原子性的读取操作接受一个对应类型的指针值,返回该指针指向的值。原子性读取意味着读取值的同时,当前计算机的任何CPU都不会进行针对值的读写操作。
比方说在32位计算架构的计算机上写入一个64位的整数时,如果在这个写操作尚未完成的时候,有一个读操作被并发的执行了,那么这个读操作很有可能会读取到一个只有被修改了一半的数据。
另一个需要注意的是for循环中,当使用v:=value为变量v赋值时,需要注意的是由于读取value的值的操作并不是并发安全的。因此在读取操作时其它对其的读写操作可能会同时发生。
先来看下它的函数和简单使用
func LoadInt32(addr *int32) (val int32)
func LoadInt64(addr *int64) (val int64)
func LoadUint32(addr *uint32) (val uint32)
func LoadUint64(addr *uint64) (val uint64)
func LoadUintptr(addr *uintptr) (val uintptr)
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
package mainimport ("fmt""sync/atomic"
)func main() {var num int32fmt.Println("num is ", num)atomic.LoadInt32(&num)fmt.Println("new num is ", atomic.LoadInt32(&num))
}
针对上述所说的问题做一个详细的解答
首先就是非原子性读取的风险:
假设有一个共享变量 value
,多个协程(goroutine)可能同时读写它。如果直接通过普通读取(如 v := value
)获取值,可能会遇到以下问题:
- 部分写入:例如在 32 位系统上写入一个 64 位整数需要两次操作(先写低 32 位,再写高 32 位)。如果读取操作在两次写入之间发生,可能读到“半新半旧”的值。
- 缓存不一致:不同 CPU 核心的缓存可能不同步,普通读取可能读到过时的缓存值,而非内存中的最新值。
这个时候就该我们的Load出手了:
- 它可以确保读操作是从内存中获取,而不是缓存
- 原子性保证,确保读到的值是完整的,不会读到部分写入
2.4 Store(存储)
- 原子性存储会将val值保存在*addr中
- 与读操作对应的写操作,sync/atomic提供了与原子值载入Load函数相对应的原子值存储Store函数,原子性存储函数均为Store开头
func StoreInt32(addr *int32, val int32)
func StoreInt64(addr *int64, val int64)
func StoreUint32(addr *uint32, val uint32)
func StoreUint64(addr *uint64, val uint64)
func StoreUintptr(addr *uintptr, val uintptr)
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
package mainimport ("fmt""sync/atomic"
)func main() {var num int32atomic.StoreInt32(&num, 10)fmt.Println("num is ", num)
}
2.5 Swap (交换)
他和比较并交换是不同的,他只有新值作为参数,但是旧值是返回的
func SwapInt32(addr *int32, new int32) (old int32)
func SwapInt64(addr *int64, new int64) (old int64)
func SwapUint32(addr *uint32, new uint32) (old uint32)
func SwapUint64(addr *uint64, new uint64) (old uint64)
func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)
func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)
package mainimport ("fmt""sync/atomic"
)func main() {var num int32old := atomic.SwapInt32(&num, 10)fmt.Println("new is ", num)fmt.Println("old is", old)
}
2.6 Or (位或)和 And (位与)
都是用于设置某些特定位(如标志位),不影响其他位。
就是用于处理bit位,专门用于做位运算的。
func OrInt32(addr *int32, mask int32) (old int32)
func OrUint32(addr *uint32, mask uint32) (old uint32)
func OrInt64(addr *int64, mask int64) (old int64)
func OrUint64(addr *uint64, mask uint64) (old uint64)
func OrUintptr(addr *uintptr, mask uintptr) (old uintptr)func AndInt32(addr *int32, mask int32) (old int32)
func AndUint32(addr *uint32, mask uint32) (old uint32)
func AndInt64(addr *int64, mask int64) (old int64)
func AndUint64(addr *uint64, mask uint64) (old uint64)
func AndUintptr(addr *uintptr, mask uintptr) (old uintptr)
三.atomic.Value
网上对这个有详细的介绍底层,感兴趣的同学可以自行上网查阅
上述的操作你会发现,他只针对了一些一部分类型,其他数据结构依旧要使用到锁,如果把这些互斥锁换成用atomic.LoadPointer/StorePointer
来做并发控制,那性能将能提升。
针对这个问题,就有人提出,在已有的atomic包的基础之上,封装出一个atomic.Value类型,这样用户就可以在不依赖Go内部类型的情况下使用原子操作了。
在他下面也提供了上述的Add,Swap,CompareAndSwap,Load和Store
type Value struct {v any
}
package mainimport ("fmt""sync/atomic""time"
)// 定义配置结构体
type Config struct {DatabaseURL stringMaxConnects intTimeout time.Duration
}func main() {// 初始化 atomic.Value,存储初始配置var config atomic.Valueconfig.Store(Config{DatabaseURL: "localhost:3306",MaxConnects: 10,Timeout: 5 * time.Second,})// 启动一个 goroutine 定时更新配置go func() {updateCount := 0for {time.Sleep(2 * time.Second) // 每2秒更新一次配置newConfig := Config{DatabaseURL: fmt.Sprintf("new_host_%d:3306", updateCount),MaxConnects: 10 + updateCount,Timeout: 5*time.Second + time.Duration(updateCount)*time.Second,}config.Store(newConfig) // 原子存储新配置updateCount++}}()// 启动多个读取协程,并发读取配置for i := 0; i < 3; i++ {go func(id int) {for {time.Sleep(500 * time.Millisecond)// 原子读取配置currentConfig := config.Load().(Config) // 类型断言fmt.Printf("Goroutine %d: Config={URL: %s, MaxConnects: %d, Timeout: %s}\n",id, currentConfig.DatabaseURL, currentConfig.MaxConnects, currentConfig.Timeout)}}(i)}// 主协程等待time.Sleep(10 * time.Second)
}
这里是以一个结构体为例,如果是字符串也是可以的
需要注意一点:
Load()
返回interface{}
,需强制转换为实际类型
换句话说就是写用 Store
,读用 Load
,类型要一致
相关文章:
Go语言的原子操作
当我们想要对某个变量并发安全的修改,除了使用官方提供的mutex,还可以使用sync/atomic包的原子操作,它能够保证对变量的读取或修改期间不被其他的协程所影响。 Golang提供的原子操作都是非侵入式的,由标准库sync/atmoic包提供&am…...
Visual Studio 2022 插件推荐
Visual Studio 2022 插件推荐 Visual Studio 2022 (简称 VS2022) 是一款强大的 IDE,适合各类系统组件、框架和应用的开发。插件是接入 VS2022 最重要的扩展方式之一,它们可以大幅提升开发效率、优化代码质量,并提供强大的调试和分析功能。 …...
【深度学习-pytorch篇】3. 优化器实现:momentum,NAG,AdaGrad,RMSProp,Adam
Optimization Algorithms Explained 1. Beale Function 与导数函数讲解 Beale 函数是一个著名的用于测试优化算法性能的函数,其具有多个局部极值点,适合评估不同优化器的表现: def beale(x1, x2):"""Beale 函数定义&#x…...

C# NX二次开发-查找连续倒圆角面
在QQ群里有人问怎么通过一个选择一个倒圆角面来自动选中一组倒圆角面。 可以通过ufun函数 UF_MODL_ask_face_type 和 UF_MODL_ask_face_props 可判断处理选择相应的一组圆角面。 代码: Tag[] 查找连续倒圆角面(Tag faceTag) {theUf.Modl.AskFaceType(faceTag, out int typ…...

今天遇到的bug
先呈现一下BUG现象。 这主要是一个传参问题,参数一直传不过去。后来我才发现,问题所在。 我们这里用的RquestBody接收参数,所有请求的参数需要用在body体中接收,但是我们用postman,用的是字符串查询方式传参&#x…...
Go语言字符串类型详解
1. 定义字符串类型 package mainimport ("fmt");func main() {var str1 string "你好 GoLang 1"var str2 "你好 GoLang 2"str3 : "你好 GoLang 3"fmt.Printf("%v--%T\n", str1, str1)// 你好 GoLang 1--stringfmt.Printf…...

长安链智能合约命令解析(全集)
创建命令解析 ./cmc client contract user create \ --contract-namefact \ --runtime-typeWASMER \ --byte-code-path./testdata/claim-wasm-demo/rust-fact-2.0.0.wasm \ --version1.0 \ --sdk-conf-path./testdata/sdk_config.yml \ --admin-key-file-paths./testdata/cryp…...

一、OpenCV的基本操作
目录 1、OpenCV的模块 2、OpenCV的基础操作 2.1图像的IO操作 2.2绘制几何图形 2.3获取并修改图像中的像素点 2.4 获取图像的属性 2.5图像通道的拆分与合并 2.6色彩空间的改变 3、OpenCV的算数操作 3.1图像的加法 3.2图像的混合 3.3总结 1、OpenCV的模块 2、OpenCV的基…...

裂缝仪在线监测装置:工程安全领域的“实时守卫者”
在基础设施运维领域,裂缝扩展是威胁建筑结构安全的核心隐患之一。传统人工巡检方式存在效率低、时效性差、数据主观性强等局限,而裂缝仪在线监测装置通过技术迭代,实现了对结构裂缝的自动化、持续性追踪,为工程安全评估提供科学依…...

【论文精读】2024 ECCV--MGLD-VSR现实世界视频超分辨率(RealWorld VSR)
文章目录 一、摘要二、问题三、Method3.1 Latent Diffusion Model3.2 Motion-guided Diffusion Sampling3.3 Temporal-aware Decoder Fine-tuning 四、实验设置4.1 训练阶段4.2 训练数据 贡献总结 论文全称: Motion-Guided Latent Diffusion for Temporally Consis…...

SpringBoot简单体验
1 Helloworld 打开:https://start.spring.io/ 选择maven配置。增加SpringWeb的依赖。 Generate之后解压,代码大致如下: hpDESKTOP-430500P:~/springboot2/demo$ tree ├── HELP.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── s…...
【系统架构设计师】2025年上半年真题论文回忆版: 论系统负载均衡设计方法(包括解题思路和参考素材)
更多内容请见: 备考系统架构设计师-专栏介绍和目录 文章目录 真题题目(2025年上半年 试题3)解题思路论文素材参考1、静态负载均衡策略2、动态负载均衡策略3、基于场景的负载均衡真题题目(2025年上半年 试题3) 请围绕 “论系统负载均衡设计方法” 论题,依次从以下三个方面…...

2025年通用 Linux 服务器操作系统该如何选择?
2025年通用 Linux 服务器操作系统该如何选择? 服务器操作系统的选择对一个企业IT和云服务影响很大,主推的操作系统在后期更换的成本很高,而且也有很大的迁移风险,所以企业在选择服务器操作系统时要尤为重视。 之前最流行的服务器…...

Azure devops 系统之五-部署ASP.NET web app
今天介绍如何通过vscode 来创建一个asp.net web app,并部署到azure 上。 创建 ASP.NET Web 应用 在您的计算机上打开一个终端窗口并进入工作目录。使用 dotnet new webapp 命令创建一个新的 .NET Web 应用,然后将目录切换到新创建的应用。 dotnet new webapp -n MyFirstAzu…...
Hadoop是什么
注:本人不懂Hadoop是什么,问的大模型,让它用生动浅显的语言向我解释。为了防止忘记,我把它说的记录下来。接下来的解释都是大模型生成的,如果有错误的地方欢迎指正 。 Hadoop 是什么?(一句…...

学习路之PHP--easyswoole_panel安装使用
学习路之PHP--easyswoole_panel安装使用 一、新建文件夹二、安装三、改配置地址四、访问 IP:Port 自动进入index.html页面 一、新建文件夹 /www/wwwroot/easyswoole_panel 及配置ftp 解压easyswoole_panel源码 https://github.com/easyswoole-panel/easyswoole_panel 二、安…...
结合 AI 编程,让前端开发更简单:趋势、方法与实践
在 AI 迅猛发展的浪潮中,前端开发正在迎来范式转变。本文将深入探讨如何将 AI 编程能力嵌入前端工程体系中,重塑前端生产力工具链与开发方式。 一、前端开发的核心痛点 尽管前端框架(如 Vue、React)已经大大简化了 UI 构建&#…...

【拓扑排序】P6560 [SBCOI2020] 时光的流逝|普及+
本文涉及知识点 C图论 拓扑排序 P6560 [SBCOI2020] 时光的流逝 题目背景 时间一分一秒的过着,伴随着雪一同消融在了这个冬天, 或许,要是时光能停留在这一刻,该有多好啊。 … “这是…我在这个小镇的最后一个冬天了吧。” “嗯…...

SSRF 接收器
接收请求 IP.php <?php // 定义日志文件路径 $logFile hackip.txt;// 处理删除请求 if (isset($_POST[delete])) {$ipToDelete $_POST[ip];$lines file($logFile, FILE_IGNORE_NEW_LINES);$newLines array();foreach ($lines as $line) {$parts explode( | , $line);…...
【设计模式】责任链
【设计模式】责任链 在实际开发中,我们经常遇到这样的需求:某个请求需要经过多个处理者,但处理的顺序、方式可能会变化或扩展。这时候,责任链模式就能派上用场。 责任链模式(Chain of Responsibility) 是…...
unix/linux source 命令,其高级使用
就像在物理学中,掌握了基本定律后,我们可以开始研究更复杂的系统和现象,source 的高级用法也是建立在对其基本行为深刻理解之上的。 让我们一起探索 source 的高级应用领域: 1. 条件化加载 (Conditional Sourcing) 根据某些条件来决定是否 source 一个文件,或者 source…...
邮件验证码存储推荐方式
邮件验证码的存储方案需要兼顾 安全性、性能 和 可维护性,以下是详细分析和推荐方案: 1. 推荐方案:Redis(首选) 为什么选择 Redis? 优势说明高性能内存读写,毫秒级响应,适合高频验…...
Allegro 输出生产数据详解
说明 用于PCB裸板的生产可以分别单独创建文件 光绘数据(Gerber)、钻孔(NC Drill)、IPC网表;或者通过ODB++或IPC2581文件(这是一个新格式),它包含生产裸板所需要的所有信息 光绘数据 Artwork Gerber 光绘数据一般包含设计中各个层面的蚀刻线路、阻焊、铅锡、字符等信…...

FastAPI MCP 快速入门教程
目录 什么是 FastAPI MCP?项目设置1. 初始化项目2. 安装依赖3. 项目结构 编写代码创建主应用文件 运行和测试1. 启动服务器2. 使用 MCP Inspector 测试 什么是 FastAPI MCP? FastAPI MCP 是一个将 FastAPI 应用程序转换为 Model Context Protocol (MCP)…...

uni-app学习笔记二十一--pages.json中tabBar设置底部菜单项和图标
如果应用是一个多 tab 应用,可以通过 tabBar 配置项指定一级导航栏,以及 tab 切换时显示的对应页。 在 pages.json 中提供 tabBar 配置,不仅仅是为了方便快速开发导航,更重要的是在App和小程序端提升性能。在这两个平台ÿ…...

【Redis】基本命令
Redis命令行客户端 现在我们已经启动了Redis服务,下面将介绍如何使用redis - cli连接、操作Redis服务。客户端和服务端的交互过程如图1 - 3所示。 redis - cli可以使用两种方式连接Redis服务器。 第一种是交互式方式:通过redis - cli -h {host} -p {p…...
爬虫工具链的详细分类解析
以下是针对爬虫工具链的详细分类解析,涵盖静态页面、动态渲染和框架开发三大场景的技术选型与核心特性: 🧩 一、静态页面抓取(HTML结构固定) 工具组合:Requests BeautifulSoup 适用场景:目标数…...
鸿蒙编译ffmpeg库
下载 ffmpeg 项目 基于如下项目编译的 ffmpeg git clone https://gitcode.com/openharmony-sig/tpc_c_cplusplus.git 配置编译环境 下载 command line tools https://developer.huawei.com/consumer/cn/download/ 导出 OHOS_SDK 环境变量 export OHOS_SDK~/command-line-…...

哈希:闭散列的开放定址法
我还是曾经的那个少年 1.概念 通过其要存储的值与存储的位置建立映射关系。 如:基数排序也是运用了哈希开放定址法的的思想。 弊端:仅适用于数据集中的情况 2.开放定址法 问题:按照上述哈希的方式,向集合插入数据为44ÿ…...

Unity-QFramework框架学习-MVC、Command、Event、Utility、System、BindableProperty
QFramework QFramework简介 QFramework是一套渐进式、快速开发框架,适用于任何类型的游戏及应用项目,它包含一套开发架构和大量的工具集 QFramework的特性 简洁性:QFramework 强调代码的简洁性和易用性,让开发者能够快速上手&a…...