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

server/2024/10/8 16:57:16/

文章目录

  • 互斥锁的使用是否能够保证绝对的原子性
  • 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 mainimport ("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 mainimport ("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.ppmy.cn/server/126161.html

相关文章

Vue3中的30个高频重点面试题

题目 1:Vue3 中 reactive 函数的内部实现原理是什么? 解答:reactive 函数是 Vue3 实现响应式数据的核心 API 之一。其内部主要基于 ES6 的 Proxy 对象来实现。当调用 reactive 函数传入一个对象时,会创建一个 Proxy 对象来拦截对…

第四章 -课后练习7[一元多项式回归拟合]一元线性回归 EXCEl实验与Python结合实现

1、首先使用excel录入数据,绘制散点图: 时序年份销售量(件)12012423.50

Pytest+selenium UI自动化测试实战实例

🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 今天来说说pytest吧,经过几周的时间学习,有收获也有疑惑,总之最后还是搞个小项目出来证明自己的努力不没有白费。 环境准备 1…

C0012.Clion改用VS编译器开发Qt界面

1.VS编译器添加 2.配置MSVC2019环境变量 3.各种问题报错与解决 问题描述 warning C4819:该文件包含不能在当前代码页(936)中表示的字符。解决办法 在CMakeLists.txt中添加如下代码 # 如下代码只在使用VS编译器时需要,使用mingw32编译器时需要注释掉 # 编码设置(用于解决wa…

服装分类检测系统源码分享

服装分类检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

SpringBoot教程(安装篇) | Docker Desktop的安装(Windows下的Docker环境)

SpringBoot教程(安装篇) | Docker Desktop的安装(Windows下的Docker环境) 前言如何安装Docker Desktop资源下载安装启动(重点)1. 检查 bcdedit的hypervisorlaunchtype是否为Auto2. 检查CPU是否开启虚拟化3.…

Ceph RocksDB 深度调优

介绍 调优 Ceph 可能是一项艰巨的挑战。在 Ceph、RocksDB 和 Linux 内核之间,实际上有数以千计的选项可以进行调整以提高存储性能和效率。由于涉及的复杂性,比较优的配置通常分散在博客文章或邮件列表中,但是往往都没有说明这些设置的实际作…

828华为云征文|WordPress部署

目录 前言 一、环境准备 二、远程连接 三、WordPress简介 四、WordPress安装 1. 基础环境安装 ​编辑 2. WordPress下载与解压 3. 创建站点 4. 数据库配置 总结 前言 WordPress 是一个非常流行的开源内容管理系统(Content Management System, CMS&#xf…