go atomic原子操作详细解读
文章目录
- 概要
- 1、基本知识
- 1.1 原子操作是什么
- 1.2 CPU怎么实现原子操作的?
- 2、atomic包
- 2.1、 Add函数
- 2.2、CompareAndSwap函数
- 2.3、Swap函数
- 2.4、Load函数
- 2.5、Store函数
- 3、atomic.Value值
概要
atomic包是golang通过对底层系统支持的原子操作进行封装,而提供的原子操作包,用于实现无锁化并发安全的操作数据。支持加法(Add)、比较并交换(CompareAndSwap)、直接交换(Swap)、设置指针变量值(Store)、获取指针变量值(Load);还设置了atomic.Value结构,支持对象存储、获取、比较并交换、直接交换操作。
atmoic包怎么用?以加法为例,变量加法运算操作并不是原子性的,包括从内存中取值放入加法寄存器、寄存器运算得到结果、将结果写回内存,在并发环境下,一个协程的运算结果可能被另一个覆盖,从而导致丢失修改的问题,例如以下代码的最后打印结果不会是10000。
var wg sync.WaitGroup
var num = 0func Add() {num++wg.Done()
}
func main() {size := 10000wg.Add(size)for i := 0; i < size; i++ {go Add()}wg.Wait()fmt.Println(num)
}
而以下代码时10000:
var wg sync.WaitGroup
var num int32 = 0func Add() {atomic.AddInt32(&num, 1)wg.Done()
}
func main() {size := 10000wg.Add(size)for i := 0; i < size; i++ {go Add()}wg.Wait()fmt.Println(num)
}
1、基本知识
1.1 原子操作是什么
原子操作是指在执行过程中不会被中断的操作,要么全部执行完成,要么完全不执行,不存在部分执行的情况。原子操作可以看作是一个不可分割的单元,其他线程或进程无法在其中间进行干扰或插入。
原子操作通常用于对共享数据进行读取、写入和修改等操作,以保证数据的一致性和正确性。在多线程或多进程环境下,如果多个线程或进程同时访问和修改同一份共享数据,没有正确的同步机制或使用原子操作可能会导致数据竞争和不确定的行为。
常见的原子操作包括:
- 原子读(Atomic Read):从内存中获取一个共享变量的值,并确保其他线程或进程不会在读取过程中修改该值。
- 原子写(Atomic Write):将一个值写入到共享变量,并确保其他线程或进程不会在写入过程中读取或修改该值。
- 原子加(Atomic Add):将一个特定的值与共享变量相加,并将结果存回共享变量中,确保其他线程或进程不会在加法过程中干扰。
- 原子比较并交换(Atomic Compare-and-Swap):比较共享变量的当前值与预期值是否相等,如果相等,则将新值写入共享变量;如果不相等,则不做任何修改。该操作常用于实现同步机制和锁。
1.2 CPU怎么实现原子操作的?
现在的CPU一般为多核处理器,它底层实现原子操作的方式有多种,下面介绍几种常见的方法:
-
总线锁定:在多核处理器中,可以使用总线锁定机制来确保原子操作。当一个处理器需要执行原子操作时,它会向总线发送一个请求锁定的信号。其他处理器在接收到该信号后,将暂停对总线的访问,以防止干扰正在进行的原子操作。只有当原子操作完成并释放锁定时,其他处理器才能继续对总线进行访问。
-
缓存一致性协议:多核处理器中的每个核心通常都具有自己的缓存,这就需要确保各个核心之间的缓存数据的一致性。缓存一致性协议可以通过在多个核心之间共享和更新处理器**缓存行(缓存在cpu核心本地缓存中的一条数据)**的状态信息来实现原子操作。常见的缓存一致性协议有MESI,多个核心更新数据时需要遵循以下原则:
-
Modified(修改)状态:当一个核心修改了一个缓存行中的数据时,该缓存行将被标记为“修改”状态。此时,其他核心的缓存中对应的缓存行就被视为无效(Invalid),即无效数据。
-
Exclusive(独占)状态:当一个核心从内存中加载了一个缓存行到自己的缓存中,并且该缓存行在其他核心的缓存中没有副本时,该缓存行处于“独占”状态。这时,其他核心需要读取或写入该数据时,必须通过该核心进行访问,独占状态的数据,可以直接进行修改。
-
Shared(共享)状态:当多个核心都拥有同一个缓存行的副本时,该缓存行处于“共享”状态。此时,对于读操作,其他核心可以直接从自己的缓存中读取数据。对于写操作,需要通过总线发出命令让其他缓存的状态更新为“失效”,该缓存中数据更新为独占,才能修改。
-
Invalid(无效)状态:当一个核心修改了一个缓存行的数据时,该缓存行在其他核心的数据状态会被标记为“无效”状态。这意味着其他核心需要重新从内存中加载最新的数据。
总的逻辑是,当一个核心读取内存中数据在自己的本地缓存中并且只有该核心的缓存拥有该缓存行,这个缓存行的状态为独占状态,此时可直接进行读写操作,其它缓存读取该缓存行需要通过总线想该核心发送请求进行读取,此时缓存行状态变为共享。核心可以读取缓存行数据,但修改需要先通过总线发起修改请求,将其它核心的该缓存行数据置为无效,再修改缓存行中数据(看起来效率很低?其实cpu会马上修改该缓存行,修改完后缓存不会马上生效,而是放入store buffer中,再反送失效请求给所有核心,由store buffer等待全部核心响应成功,再生效缓存行的修改,这段期间核心可以继续做其它事情)。修改完成后,缓存行为修改状态,其他核心的该缓存行为无效状态,此时若有核心读取该缓存行,就将缓存行写入内存,该核心上的缓存行状态更新为共享;若是修改请求,就将核心缓存行状态更新为无效。
-
-
原子指令:现代的多核处理器通常会提供一些原子指令,例如比较并交换(compare and swap)、加载和存储条件(load-linked/store-conditional)等。这些指令能够以硬件级别的原子方式执行,从而保证对共享数据的访问是原子的。通过使用这些原子指令,可以避免锁的开销,并提供更高效的原子操作。
2、atomic包
Golang的atomic包的原子操作是通过CPU指令实现的。在大多数CPU架构中,原子操作的实现都是基于32位或64位的寄存器。Golang的atomic包的原子操作函数会将变量的地址转换为指针型的变量,并使用CPU指令对这个指针型的变量进行操作。
Golang的atomic包提供了一组原子操作函数,包括Add、CompareAndSwap、Load、Store、Swap等函数。这些函数的具体作用如下:
- Add函数:用于对一个整数型的变量进行加法操作,并返回新的值。
- CompareAndSwap函数:用于比较并交换一个指针型的变量的值。如果变量的值等于旧值,就将变量的值设置为新值,并返回true;否则,不修改变量的值,并返回false。
- Load函数:用于获取一个指针型的变量的值。
- Store函数:用于设置一个指针型的变量的值。
- Swap函数:用于交换一个指针型的变量的值,并返回旧值。
让我们更具体地来看一下Golang的atomic包的原子操作:
2.1、 Add函数
Add函数用于对一个整数型的变量进行加法操作,并返回新的值。Add函数的定义如下:
func AddInt32(addr *int32, delta int32) (new int32)
func AddInt64(addr *int64, delta int64) (new int64)
func AddUint32(addr *uint32, delta uint32) (new uint32)
func AddUint64(addr *uint64, delta uint64) (new uint64)
func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)
其中,addr表示要进行加法操作的变量的地址,delta表示要加上的值。Add函数会将变量的值加上delta,并返回新的值。
2.2、CompareAndSwap函数
CompareAndSwap函数用于比较并交换一个指针型的变量的值。如果变量的值等于旧值,就将变量的值设置为新值,并返回true;否则,不修改变量的值,并返回false。CompareAndSwap函数的定义如下:
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)
其中,addr表示要进行比较和交换的变量的地址,old表示旧值,new表示新值。如果变量的值等于旧值,就将变量的值设置为新值,并返回true;否则,不修改变量的值,并返回false。
2.3、Swap函数
Swap函数用于交换一个指针型的变量的值,并返回旧值。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)
其中,addr表示要交换的变量的地址,new表示新值。Swap函数会将变量的值设置为new,并返回旧值。
2.4、Load函数
Load函数用于获取一个指针型的变量的值。Load函数的定义如下
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)
2.5、Store函数
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)
其中,addr表示要设置的变量的地址,val表示要设置的值。Store函数会将变量的值设置为val。
3、atomic.Value值
可以用来存储any类型对象的结构体,存取操作均是原子操作,具体方法如下:
type Value struct {v any
}
// 加载Value中存的值
func (v *Value) Load() (val any) {}
// 存储对象放入Value中
func (v *Value) Store(val any) {}
// 交换Value中存的数据
func (v *Value) Swap(new any) (old any) {)
// 比较并存储(值必须铜类型且可比较采用使用该方法,否则panic);传入old、new对象,如果old对象等于Value内存储的对象,就将新的对象存入,返回treu。否则false
func (v *Value) CompareAndSwap(old, new any) (swapped bool) {}
实践一下:
package mainimport ("fmt""sync/atomic"
)type Config struct {Addr string
}var config atomic.Valuefunc main() {conf1 := Config{Addr: "1.1.1.1",}conf2 := Config{Addr: "2.2.2.2",}config.Store(conf1)oldData := config.Swap(conf2)newData := config.Load()fmt.Println(oldData, newData)conf3 := Config{Addr: "3.3.3.3",}ok := config.CompareAndSwap(conf1, conf3)fmt.Println(ok, config.Load())ok = config.CompareAndSwap(conf2, conf3)fmt.Println(ok, config.Load())
}
输出:
{1.1.1.1} {2.2.2.2}
false {2.2.2.2}
true {3.3.3.3}
相关文章:
go atomic原子操作详细解读
文章目录 概要1、基本知识1.1 原子操作是什么1.2 CPU怎么实现原子操作的? 2、atomic包2.1、 Add函数2.2、CompareAndSwap函数2.3、Swap函数2.4、Load函数2.5、Store函数 3、atomic.Value值 概要 atomic包是golang通过对底层系统支持的原子操作进行封装,…...
Vue用JSEncrypt对长文本json加密以及发现解密失败
哈喽 大家好啊,最近发现进行加密后 超长文本后端解密失败,经过看其他博主修改 JSEncrypt原生代码如下: // 分段加密,支持中文JSEncrypt.prototype.encryptUnicodeLong function (string) {var k this.getKey();//根据key所能编…...
Excel/PowerPoint折线图从Y轴开始(两侧不留空隙)
默认Excel/PowerPoint折线图是这个样子的: 左右两侧都留了大块空白,很难看 解决方案 点击横坐标,双击,然后按下图顺序点击 效果...
C++的类成员对齐
这是个小语法点,之前我们的对齐方式都是使用#pragma pack,这个方式实际是依赖编译器,且粒度粗(如果#pragma pack(1)之后没有#pragma pack(),那就作用整个进程了)。在C11之后引入关键字alignas,以此来实现对齐更加便利,…...
敏感挂载userhelper容器逃逸复现
目录 前言 分析 实验 前言 分析 实验 # Creates a payload cat "#!/bin/sh" > /evil-helper cat "ps > /output" >> /evil-helper chmod x /evil-helper # Finds path of OverlayFS mount for container # Unless the configuration ex…...
深度解读Promise.prototype.finally
由一个问题引发的血案: 手写源码实现Promise.prototype.finally。 我们知道,对于promise来讲,当状态敲定,无论状态兑现或拒绝时都需要调用的函数,可以使用Promise.prototype.finally的回调来实现。那么如何手写实现Pro…...
如何实现24/7客户服务自动化?建设智能客服知识库
客户自助服务是指用户通过企业或者第三方建立的网络平台或者终端,实现相关的自定义处理。实现客户服务自动化,对提高客户满意度、维持客户关系至关重要。客户服务自动化可以帮助企业以更快的速度和更高的效率来满足客户的售后服务要求,以进一…...
和鲸 ModelWhale 与中科可控多款服务器完成适配认证,赋能中国云生态
当前世界正处于新一轮技术革命及传统产业数字化转型的关键期,云计算作为重要的技术底座,其产业发展与产业规模对我国数字经济的高质量运行有着不可取代的推动作用。而随着我国数字上云、企业上云加快进入常规化阶段,云计算承载的业务应用越来…...
selenium +Jmeter 的性能测试
通过Jmeter快速将已有的Selenium 代码以性能测试的方式组织起来,并使用JMeter 丰富的报表展示测试结果 from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By driver …...
探索高效的HTTP异步接口测试方法:从轮询等待到自动化方案
本文将深入探讨HTTP异步接口测试的多个方面,包括轮询等待、性能测试以及自动化方案。通过详细的解释和实际案例,帮助您了解如何有效地测试异步接口,确保系统的稳定性和性能。 在现代软件开发中,HTTP异步接口扮演着至关重要的角色&…...
Android资深工程书之LiveData核心组件原理剖析
LiveData是Android架构组件库中的一个类,用于在应用程序组件之间共享数据。它是一种可观察的数据持有者,可以感知应用程序组件的生命周期,并在数据发生变化时通知观察者。 使用LiveData 在Android应用程序中使用LiveData,你可以…...
Vue的五种方法实现加减乘除运算
五种方法的详细说明: 计算属性(Computed Properties): 计算属性是Vue.js提供的一种便捷的属性,它根据依赖的数据动态计算出一个新的值。计算属性的值会被缓存,只有当依赖的数据发生变化时,才会…...
C++(1)Linux基础知识
经济下行,计算机就业形势严峻,为了勉励自己继续进步,继续学习代码提高核心竞争力。 安装QT Creator 首先,安装QT开发工具QT Creator 参考:2021最新Qt6开发环境(Qt Creator)安装以及卸载记录_q…...
接口自动化yaml文件读取与写入
前言 在走进yaml文件之前大家应该都很想知道他是用来干嘛的? 是的是的,他是用来做接口自动化测试的。 我们一起来学习他吧!——(一定要收藏带走哦❤) 1、yaml文件有什么作用呢? ①可作为配置文件使用—…...
Java Map、JSONObject、实体类互转
文章目录 前言Map、JSONObject、实体类互转 前言 使用库 com.alibaba.fastjson2,可完成大部分JSON转换操作。 详情参考文章: Java FASTJSON2 一个性能极致并且简单易用的JSON库 Map、JSONObject、实体类互转 import com.alibaba.fastjson2.JSON; import com.alib…...
在Hive/Spark上执行TPC-DS基准测试 (PARQUET格式)
在上一篇文章:《在Hive/Spark上运行执行TPC-DS基准测试 (ORC和TEXT格式)》中,我们介绍了如何使用 hive-testbench 在Hive/Spark上执行TPC-DS基准测试,同时也指出了该项目不支持parquet格式。 如果我们想要生成parquet格式的测试数据,就需要使用其他工具了。本文选择使用另…...
基于CentOS搭建私有仓库harbor
环境: 操作系统:CentOS Linux 7 (Core) 内核: Linux 3.10.0-1160.el7.x86_64 目录 安装搭建harbor (1)安装docker编排工具docker compose (2)下载Harbor 安装包 (3&…...
PDF怎么转Word?8 个最佳 PDF 转 Word 转换器
PDF 转 Word 转换工具只是一个特殊程序,可以将 PDF(本机和/或扫描)转换为 Microsoft Office Word 格式。将 PDF 导出到 Word 的主要原因之一是满足可编辑文档的需求,尽管还有其他原因。 由于缺少 PDF 阅读器,您可以选…...
老板都爱看的财务数据分析报表,全在这了
老板们都爱看哪些财务数据分析报表?自然是可以帮助他们更好地了解公司的财务状况和经营绩效的那一类财务数据分析报表,比如利润表、资产负债表、现金流量表、应收账款分析报表、应付账款分析报表、库存分析报表等。奥威BI数据可视化工具有一套标准化财务…...
ZooKeeper(zk)与 Eureka 的区别及集群模式比较分析
作者:zhaokk 推荐阅读 AI文本 OCR识别最佳实践 AI Gamma一键生成PPT工具直达链接 玩转cloud Studio 在线编码神器 玩转 GPU AI绘画、AI讲话、翻译,GPU点亮AI想象空间 资源分享 「java、python面试题」来自UC网盘app分享,打开手机appÿ…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...
9-Oracle 23 ai Vector Search 特性 知识准备
很多小伙伴是不是参加了 免费认证课程(限时至2025/5/15) Oracle AI Vector Search 1Z0-184-25考试,都顺利拿到certified了没。 各行各业的AI 大模型的到来,传统的数据库中的SQL还能不能打,结构化和非结构的话数据如何和…...
使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...
