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 (增或减)

  1. 用于处理增加和减少的原子操作,函数名以Add为前缀,后跟针对特定类型的名称。
  2. 原子增被操作的只能是数值类型,即int32,int64,uint32,uint64,uinptr。
  3. 原子增减函数的第一个参数为原值,第二个是要增多少。
  4. 方法:
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出手了:

  1. 它可以确保读操作是从内存中获取,而不是缓存
  2. 原子性保证,确保读到的值是完整的,不会读到部分写入

2.4 Store(存储)

  1. 原子性存储会将val值保存在*addr中
  2. 与读操作对应的写操作,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,类型要一致

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/85334.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

QNAP MEMOS 域名访问 SSL(Lucky)

注意&#xff1a;下述是通过ssh、docker-compose方式安装docker的&#xff0c;不是直接在container station中安装的哈&#xff01;&#xff01;&#xff01; 一、编辑docker-compose.yml文件 用“#”号标识的&#xff0c;在保存文件的时候建议去掉&#xff0c;不然有时候会出…

C#实现远程锁屏

前言 这是一次提前下班没有锁屏进而引发的一次思考后的产物&#xff0c;思考的主要场景是当人离开电脑后&#xff0c;怎么能控制电脑锁屏&#xff0c;避免屏幕上的聊天记录被曝光。 首先想到通过系统的电源计划设置闲置超时时间熄屏&#xff0c;这可能是最接近场景的解决方案&a…

[Protobuf]常见数据类型以及使用注意事项

[Protobuf]常见数据类型以及使用注意事项 水墨不写bug 文章目录 一、基本数据类型1、字段2、字段的修饰规则 二、自定义数据类型1、message类型2、enum类型3、Any类型4、oneof类型5、map类型 三、小工具1.hexdump2.decode 四、注意事项 一、基本数据类型 protobuf 支持多种基础…

JS分支和循环

程序的执行顺序 在程序开发中&#xff0c;程序有三种不同的执行顺序 1.顺序执行 2.分支执行 3.循环执行 程序的代码块 <script>//一个代码块{var num11var num22var num3num1num2}//一个休想var info{name:"chen",age:18} 1.if分支语句&#xff08;单分支语句&…

Android 开发 Kotlin 全局大喇叭与广播机制

在 Android 开发中&#xff0c;广播机制就像一个神通广大的 “消息快递员”&#xff0c;承担着在不同组件间传递信息的重任。Kotlin 语言的简洁优雅更使其在广播机制的应用中大放异彩。今天&#xff0c;就让我们一同深入探索 Android 开发中 Kotlin 全局大喇叭与广播机制的奥秘…

rabbitmq AI复习

RabbitMq rabbitmq &#x1f9d1;‍&#x1f4bb; User 帮我复习rabbitmq相关知识&#xff0c;我是一个经验丰富的程序员 &#x1f916; Assistant 好的&#xff01;很高兴能通过这种方式帮你复习或学习 RabbitMQ 的知识。按照你说的流程&#xff0c;我们从完全零基础开始&…

计算机视觉---YOLOv5

YOLOv5理论讲解 一、YOLOv5 整体架构解析 YOLOv5 延续了 YOLO 系列的 单阶段目标检测框架&#xff0c;包含 主干网络&#xff08;Backbone&#xff09;、颈部网络&#xff08;Neck&#xff09; 和 检测头&#xff08;Head&#xff09;&#xff0c;但在结构设计上更注重 轻量化…

C++多重继承详解与实战解析

#include <iostream> using namespace std; //基类&#xff0c;父类 class ClassA { public:void displayA() {std::cout << "Displaying ClassA" << std::endl;}void testFunc(){std::cout << "testFunc ClassA" << std::e…

单细胞注释前沿:CASSIA——无参考、可解释、自动化细胞注释的大语言模型

细胞类型注释是单细胞RNA-seq分析的重要步骤&#xff0c;目前有许多注释方法。大多数注释方法都需要计算和特定领域专业知识的结合&#xff0c;而且经常产生不一致的结果&#xff0c;难以解释。大语言模型有可能在减少人工输入和提高准确性的同时扩大可访问性&#xff0c;但现有…

STM32Cubemx-H7-17-麦克纳姆轮驱动

前言 --末尾右总体的.c和.h 本篇文章把麦克纳姆轮的代码封装到.c和.h&#xff0c;使用者只需要根据轮子正转的方向&#xff0c;在.h处修改定义方向引脚&#xff0c;把轮子都统一正向后&#xff0c;后面的轮子驱动就可以正常了&#xff0c;然后直接调用函数驱动即可。 设置满…

文档核心结构优化(程序C++...)

文档核心结构优化 一、文档核心结构优化二、C关键特性详解框架2.1 从C到C的范式迁移 三、深度代码解析模板3.1 现代C特性分层解析 四、C vs C 关键差异矩阵五、交互式文档设计策略5.1 三维学习路径5.2 代码缺陷互动区 六、现代C特性演进图七、性能优化可视化呈现&#xff08;深…

PyTorch ——torchvision数据集使用

如果下载的很慢&#xff0c;可以试试下面这个

纯前端实现图片伪3D视差效果

作者&#xff1a;vivo 互联网前端团队- Su Ning 本文通过depth-anything获取图片的深度图&#xff0c;同时基于pixi.js&#xff0c;通过着色器编程&#xff0c;实现了通过深度图驱动的伪3D效果。该方案支持鼠标/手势与手机陀螺仪双模式交互&#xff0c;在保证性能的同时&#x…

英语写作中“专注于”focus on、concentrate的用法

Focus on在论文写作中常用&#xff0c;指出研究点&#xff0c;例如&#xff1a; There are three approaches to achieving ID authentication. Our study will focus on ……&#xff08;有三种途径实现身份认证&#xff0c;我们的研究专注于……&#xff09; concentrate &…

go环境配置

下载对应版本的 go 版本 https://go.dev/dl/ 配置 vim ~/.zshrc export GOROOT/usr/local/go export PATH$PATH:$GOROOT/binsource ~/.zshrc >>>>>> go versiongoland 配置&#xff1a; &#x1f50d; 一、什么是GOPATH&#xff1f; GOPATH 是旧的项目结…

AI Agent智能体:底层逻辑、原理与大模型关系深度解析·优雅草卓伊凡

AI Agent智能体&#xff1a;底层逻辑、原理与大模型关系深度解析优雅草卓伊凡 一、AI Agent的底层架构与核心原理 1.1 AI Agent的基本构成要素 AI Agent&#xff08;人工智能代理&#xff09;是一种能够感知环境、自主决策并执行行动的智能系统。其核心架构包含以下关键组件…

【手搓一个原生全局loading组件解决页面闪烁问题】

页面闪烁效果1 页面闪烁效果2 封装一个全局loading组件 class GlobalLoading extends HTMLElement {constructor() {super();this.attachShadow({ mode: open });}connectedCallback() {this.render();this.init();}render() {this.shadowRoot.innerHTML <style>.load…

unix/linux source 命令,其高级使用

就像在物理学中,掌握了基本定律后,我们可以开始研究更复杂的系统和现象,source 的高级用法也是建立在对其基本行为深刻理解之上的。 让我们一起探索 source 的高级应用领域: 1. 条件化加载 (Conditional Sourcing) 根据某些条件来决定是否 source 一个文件,或者 source…

DexGarmentLab 论文翻译

单个 专家 演示 装扮 15 任务 场景 2500+ 服装 手套 棒球帽 裤子 围巾 碗 帽子 上衣 外套 服装-手部交互 捕捉 摇篮 夹紧 平滑 任务 ...... 投掷 悬挂 折叠 ... 多样化位置 ... 多样化 变形 ... 多样化服装形状 类别级 一般化 类别级(有或没有变形) 服装具有相同结构 变形 生…

WPF-Prism学习笔记之 “导航功能和依赖注入“

新建空白模板(Prism) 新建好后会有自动创建ViewModels和Views 在"MainWindow.xaml"文件里面标题去绑定了一个属性"Title"&#xff0c;而"MainWindowViewModel.cs"里面继承一个非常重要的"BindbleBase"(prism框架里面非常重要的)。所以…