Go基础学习10-原子并发包sync.atomic的使用:CSA、Swap、atomic.Value......

news2024/10/3 3:24:17

文章目录

  • 互斥锁的使用是否能够保证绝对的原子性
  • Go语言的原子包sync.atomic
  • sync.Value使用
  • 互斥锁与原子类的关系以及代码中应该如何选择

互斥锁的使用是否能够保证绝对的原子性

在之前文章中介绍了sync.Mutex变量用于保证并发操作时数据安全性。但是否sync.Mutex变量能够保证操作的原子性。不能。对于单个语句本身是原子性的,只要执行到这条语句就一定是原子性的。对于sync.Mutex变量包裹的语句并非单条原子语句。
sync.Mutex变量能够保证一个 goroutine 在执行临界区中的代码时,不被其他的goroutine 打扰,Go语言中的运行时系统中的调度器,会协调不同goroutine,使得一定时刻只能运行一定数量的goroutine。 所以即使加了sync.Mutex修饰临界区,当前goroutine也可能在调度器的作用下中断执行,并让它由运行状态转为非运行状态。
互斥锁可以保证临界区中代码的串行执行,但却不能保证这些代码执行的原子性(atomicity)

Go语言的原子包sync.atomic

在原子包sync.atomic中的函数可以做的原子操作有:

  • 加法(add)

  • 比较并交换(compare and swap:CAS)

  • 加载(Load)

  • 存储(stroe)

  • 交换(swap)

    对于上述原子操作类型中的每一个,可以支持的数据类型有:==int32、int64、uint32、uint64、uintptr,以及unsafe包中的Pointer。==不过,针对unsafe.Pointer类型,该包并未提供进行原子加法操作的函数。

下面以atomic.Addint32以及atomic.AddUint32为例编写代码如下:

// ------------------------------------------------------------
// package atomic
// @file      : demo01.go
// @author    : WeiTao
// @contact   : 15537588047@163.com
// @time      : 2024/10/2 20:44
// ------------------------------------------------------------
package main

import (
	"fmt"
	"sync/atomic"
)

func main() {
	a := int32(0)
	a = atomic.AddInt32(&a, int32(23))
	fmt.Println(a)
	swapBool := atomic.CompareAndSwapInt32(&a, int32(23), int32(45))
	if swapBool {
		fmt.Printf("a swap val is : %v\n", a)
	}

	// 验证,通过AddUint32执行原子减法
	fmt.Println("===========================")
	b := uint32(18)
	delat := int32(-3)
	b = atomic.AddUint32(&b, uint32(delat))
	fmt.Println("b sub 3 result is : ", b)
	// 计算机系统中补码表示为:源码 求反 + 1.
	// ^uint32(-(-3) - 1)表示的补码与int32(-3)表示的补码相同的
	b = atomic.AddUint32(&b, ^uint32(-(-3)-1))
	fmt.Println("b sub 3 result is : ", b)
}

需要注意由于atomic.AddInt32()

// AddInt32 atomically adds delta to *addr and returns the new value.
// Consider using the more ergonomic and less error-prone [Int32.Add] instead.
func AddInt32(addr *int32, delta int32) (new int32)

将变量delta对应的值添加到指针addr地址对应的变量上,所以第一个类型必须传递指针,不能传递值,传递值的话就是拷贝一个副本,并不能对原变量值进行修改。

sync.Value使用

根据上一章节能够得知,对于go中原生的支持原子操作的函数支持的变量类型int32、int64、uint32、uint64、uintptr,以及unsafe包中的Pointer。为了弥补上述支持的类型变量的缺陷,go语言提供一个sync.Value类型。这个类型相当于一个容器,可以被用来原子地存储和加载任意的值。
atomic.Value类型是开箱即用的,我们声明一个该类型的变量(以下简称原子变量)之后就可以直接使用了。
atomic.Value类型变量提供四个操作:

// Load returns the value set by the most recent Store.
// It returns nil if there has been no call to Store for this Value.
func (v *Value) Load() (val any) 

// Store sets the value of the [Value] v to val.
// All calls to Store for a given Value must use values of the same concrete type.
// Store of an inconsistent type panics, as does Store(nil).
func (v *Value) Store(val any)

// Swap stores new into Value and returns the previous value. It returns nil if
// the Value is empty.
//
// All calls to Swap for a given Value must use values of the same concrete
// type. Swap of an inconsistent type panics, as does Swap(nil).
func (v *Value) Swap(new any) (old any)

// CompareAndSwap executes the compare-and-swap operation for the [Value].
//
// All calls to CompareAndSwap for a given Value must use values of the same
// concrete type. CompareAndSwap of an inconsistent type panics, as does
// CompareAndSwap(old, nil).
func (v *Value) CompareAndSwap(old, new any) (swapped bool)

根据上述函数定义可以得知atomic.Value变量使用时的一些限制:

  • 不能用原子值存储nil。也就是说,我们不能把nil作为参数值传入原子值的Store方法,否则就会引发一个panic。
  • 我们向原子值存储的第一个值,决定了它今后能且只能存储哪一个类型的值。
  • 交换操作和比较并交换操作均不能交换nil。
  • 当atomic.Value类型的变量被真正使用(存储值)后,他就不能再被复制了。

使用代码示例:

// ------------------------------------------------------------
// package main
// @file      : atomicValue.go
// @author    : WeiTao
// @contact   : 15537588047@163.com
// @time      : 2024/10/2 21:44
// ------------------------------------------------------------
package main

import (
	"fmt"
	"sync/atomic"
)

func main() {
	// 创建atomic.Value类型的值
	box := atomic.Value{}
	// 错误示例:构建一个切片并直接存储
	 slice := []int{1, 2, 3}
	// box.Store(slice)
	fmt.Println(box.Load().([]int))
	fmt.Println(box.Load().([]int))

	// 正确的存储,由于切片是引用类型,使用方式一进行存储的话在另一个goroutine中也可以改变存储的底层的slice的值,使用下述方法
	// 直接创建一个本地的切片,并复制原有切片,指针和外界没有关系。
	store := func(v []int) {
		replica := make([]int, len(v))
		copy(replica, v)
		box.Store(replica)
	}
	store(slice)
	fmt.Println(box.Load().([]int))

	// 调用atomic.Value的Swap方法
	oldSlice := (box.Swap([]int{3, 5})).([]int)
	fmt.Printf("old slice: %v\n", oldSlice)
	fmt.Printf("new slice:  %v\n", box.Load().([]int))
	// 调用atomic.Value的CAS方法,下面的方法是实现不了的,由于切片是引用类型不支持比较
	// box.CompareAndSwap([]int{3, 5}, []int{9, 10})
	fmt.Println(box.Load().([]int))
}

注意:我上面对于切片的CAS操作是不正确的,Go语言中对于切片、函数、字典(map)都是引用类型,都不能进行比较,会引发panic。所以即使atomic.Value类型可以用于存储切片等引用类型,但尽量不要使用CAS操作。同时存储切片类型的话不能直接将切片类型直接存储到stomic.Value变量类型,由于切片是引用类型,在其他goroutine也可以更改其值。最好参考上述代码的方式构建一个副本并进行存储。

	slice := []int{1, 2, 3}
	store := func(v []int) {
		replica := make([]int, len(v))
		copy(replica, v)
		box.Store(replica)
	}
	store(slice)

互斥锁与原子类的关系以及代码中应该如何选择

  • 原子操作明显比互斥锁要更加轻便,但是限制也同样明显。所以,我们在进行二选一的时候通常不会太困难。
  • 原子值与互斥锁之间的选择有时候就需要仔细的考量了。

使用原子值尽量遵循如下几条原则:

  • 不要对外暴露原子变量、不要传递原子值及其指针值、尽量不要在原子值中存储引用类型的值。
  1. 不要把内部使用的原子值暴露给外界。比如,声明一个全局的原子变量并不是一个正确的做法。这个变量的访问权限最起码也应该是包级私有的。
  2. 如果不得不让包外,或模块外的代码使用你的原子值,那么可以声明一个包级私有的原子变量,然后再通过一个或多个公开的函数,让外界间接地使用到它。注意,这种情况下不要把原子值传递到外界,不论是传递原子值本身还是它的指针值。
  3. 如果通过某个函数可以向内部的原子值存储值的话,那么就应该在这个函数中先判断被存储值类型的合法性。若不合法,则应该直接返回对应的错误值,从而避免 panic 的发生。
  4. 如果可能的话,我们可以把原子值封装到一个数据类型中,比如一个结构体类型。这样,我们既可以通过该类型的方法更加安全地存储值,又可以在该类型中包含可存储值的合法类型信息。

以后会再补充一些关于原子操作以及原子值的更详细的讲解,本篇只能作为入门参考,后续深入学习后继续补充。

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

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

相关文章

查找与排序-快速排序

排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序…

高效论文写作指南:那些你必须知道的工具与平台

学境思源,一键生成论文初稿: AcademicIdeas - 学境思源AI论文写作 在完成论文写作过程中,有许多实用的工具和平台可以帮助提高效率、确保质量,并保持学术规范。以下是一些常用的论文写作工具和平台: 1. 文献管理工具…

数据网格:数据去中心化的特征

在现代的数据管理架构理念中,常常会谈及数据网格,将它用来解决大规模、复杂数据环境下的数据管理和利用问题。本文将探讨数据网格的概念以及数据去中心化和数据网格的紧密联系。 一数据网格 数据网格定义:数据网格将数据视为一种产品&#x…

中原台球展,2025郑州台球展会,中国台球产业链发展大会

阳春三月,万物复苏,商机无限;品牌宣传正当季,产品招商正当时,新品发布好时期。抓住台球发展的这波财富机遇,借助壹肆柒郑州台球展这个超级平台,将品牌和产品快速打造成为覆盖全国市场的顶流。20…

Acwing 简单博弈论

公平组合游戏ICG 若一个有限满足:由两名玩家交替行动: 在游戏进程的任意时刻;可以执行的合法行动与轮到哪名玩家无关;不能行动的玩家判负; 则称该游戏为一个公平组合游戏。NIM博弈属于公平组合游戏,但城建…

鸢尾花书实践和知识记录[编程1-11二维和三维可视化]

作者空间 文章目录 思维导图函数使用 二维可视化方案平面散点图散点图的示例代码1:绘制鸢尾花的散点图代码2Plotly绘制散点图 数据类型和绘图工具的对应 平面等高线代码3生成等高线网格数据 plotly.express关键的绘图函数 Plotly的另一个模块代码4 Plotly生成的 热图…

以旅游购物贸易方式报关出口的货物是什么意思

旅游购物贸易方式的定义 ‌旅游购物贸易‌是指国内外旅游者通过旅游渠道,携带外币现钞、外币票据入境,到外汇指定银行兑换成人民币,并在特定区域进行商品采购、报检、报关后,将所购货物运回国内进行销售的贸易行为。这种贸易方式通…

官方外卖霸王餐对接接口渠道如何选择?

对接官方外卖霸王餐接口渠道通常涉及以下步骤: 选择服务提供商:选择一个提供外卖霸王餐API接口服务的平台。注册与申请:在选定服务提供商的平台上进行注册并创建账号,然后提交API接口使用申请。获取接口文档和密钥:申…

CSS基础-常见属性

6、CSS三大特性 6.1 层叠性 如果样式发生冲突,则按照优先级进行覆盖。 6.2 继承性 元素自动继承其父元素、祖先元素所设置的某些元素,优先继承较近的元素。 6.3 优先级 6.3.1 简单分级 1、内联样式2、ID选择器3、类选择器/属性选择器4、标签名选择器/…

SpringBoot框架下的社区医院信息系统开发

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及,互联网成为人们查找信息的重要场所,二十一世纪是信息的时代,所以信息的管理显得特别重要。因此,使用计算机来管理社区医院信息平台的相关信息成为必然。开发…

钢琴灯品牌排行榜前十名有哪些?护眼灯钢琴灯品牌排行榜

钢琴灯品牌排行榜前十名有哪些?要说近期比较火爆的家电产品,那一定绕不开护眼灯钢琴灯。作为能够提高光线质量,帮助我们营造舒适环境的热门好物,其受到了很多专业人士的认可。但是作为一名专业的家电测评师,我想在此提…

找到字符串中第一个匹配项的下标(c语言)

1./给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。 //示例 1: //输入:haystac…

plt用数组显示图像

目录 plt.imshow实战 plt.imshow plt.imshow 可以用来显示二维数组表示的图像,也可以用来显示热图(heatmap)等。基本语法如下: plt.imshow(X, cmapviridis, interpolationnearest)参数说明: X:二维数组&…

基础岛第5关:XTuner微调个人小助手认知

进入开发机 克隆Tutorial仓库 mkdir -p /root/InternLM/Tutorialgit clone -b camp3 https://github.com/InternLM/Tutorial /root/InternLM/Tutorial 创建虚拟环境 在安装 XTuner 之前,我们需要先创建一个虚拟环境。使用 Anaconda 创建一个名为 xtuner0121 的虚拟…

工作日志:nvm版本控制遇到的一系列问题。

1、安装vue3可使用的富文本编辑器。(https://www.wangeditor.com/v5/for-frame.html#demo-1) npm install wangeditor/editor-for-vuenext --save2、为同时拥有两个类的元素设置样式,组合选择器是通过在选择器中并列写入两个类名来实现的&am…

openpnp - 执行M400命令后,超时错误的解决方法

文章目录 openpnp - 执行M400命令后,超时错误的解决方法概述笔记备注END openpnp - 执行M400命令后,超时错误的解决方法 概述 在X轴齿隙校正时,出现M400的命令超时错误。能重现。 查了资料,有人遇到过。看了github上的一个帖子(…

STM32(四)LED闪烁、流水灯及蜂鸣器操作

小节任务:在对GPIO函数初始化操作及配置好输入或输出模式后,使用GPIO的输入输出函数控制LED闪烁、流水灯及蜂鸣器操作,本小节先使用GPIO的四个输出函数 SetBits函数将指定端口设置为高电平 ResetBits函数将指定端口设置为低电平 WriteBit根据…

Tiny-universe手戳大模型TinyRAG--task4

TinyRAG 这个模型是基于RAG的一个简化版本,我们称之为Tiny-RAG。Tiny-RAG是一个基于RAG的简化版本,它只包含了RAG的核心功能,即Retrieval和Generation。Tiny-RAG的目的是为了帮助大家更好的理解RAG模型的原理和实现。 1. RAG 介绍 LLM会产…

Linux中的进程间通信之共享内存

共享内存 共享内存示意图 共享内存数据结构 struct shmid_ds {struct ipc_perm shm_perm; /* operation perms */int shm_segsz; /* size of segment (bytes) */__kernel_time_t shm_atime; /* last attach time */__kernel_time_t shm_dtime; /* last detach time */__kerne…

OpenGL笔记之事件驱动设计将相机控制类和应用程序类分离

OpenGL笔记之事件驱动设计将相机控制类和应用程序类分离 —— 2024-10-02 下午 code review! 文章目录 OpenGL笔记之事件驱动设计将相机控制类和应用程序类分离1.代码图片2.分析3.UML4.代码 1.代码图片 运行 Mouse button 1 pressed at (100, 200) Mouse dragged by (50, 50)…