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

devtools/2024/10/9 2:56:50/

文章目录

  • 互斥锁的使用是否能够保证绝对的原子性
  • 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/devtools/123154.html

相关文章

车辆重识别(2021ICML改进的去噪扩散概率模型)论文阅读2024/9/29

所谓改进的去噪扩散概率模型主要改进在哪些方面: ①对数似然值的改进 通过对噪声的那个方差和T进行调参,来实现改进。 ②学习 这个参数也就是后验概率的方差。通过数据分析,发现在T非常大的情况下对样本质量几乎没有影响,也就是说…

Spring面试内容大纲

由gpt生成的关于Spring的面试内容大纲,可以根据大纲去拓展各部分的内容。 1. Spring框架概述 Spring的核心特性是什么? Spring的核心特性包括:依赖注入(DI)、面向切面编程(AOP)、事务管理、MV…

springboot 打包部署jsp页面两种方式war/jar

springboot 两种部署方式jsp页面 war包部署jsp页面 我们是用传统的war包,放到 tomcat的webapp目录里面,当容器启动的时候,会自动解压.war 文件,从而进行访问,但是 springboot 是内嵌的tomcat,所以我们需要排除内嵌的tomcat 使之失效 1、排除依赖jar包 <dependency>…

`pandas` 库提供了一个非常方便的方法将 DataFrame 转换为字典

在 Python 中&#xff0c;pandas 库提供了一个非常方便的方法将 DataFrame 转换为字典。DataFrame.to_dict() 方法可以根据你的需求将 DataFrame 转换为不同类型的字典。以下是一些常见的用法示例&#xff1a; 转换为字典的列表&#xff08;默认行为&#xff09;&#xff1a; 每…

如何让70B参数的大型语言模型在资源有限的边缘设备上高效运行?

你有没有想过,像我们平时使用的智能手机、家里的智能音箱这样的小设备,也能运行那些参数量高达数十亿的大型语言模型(LLM)呢?这听起来像是天方夜谭,毕竟这些模型动辄需要巨大的算力和存储资源,但实际上,随着技术的发展,这个梦想正在变成现实。那么,问题来了,怎么在资…

Pikachu-url重定向-不安全的url跳转

不安全的url跳转 不安全的url跳转问题可能发生在一切执行了url地址跳转的地方。如果后端采用了前端传进来的(可能是用户传参,或者之前预埋在前端页面的url地址)参数作为了跳转的目的地,而又没有做判断的话就可能发生"跳错对象"的问题。 url跳转比较直接的危害是: …

qt QMessageBox案例,2024.10.8

当用户点击取消按钮&#xff0c;弹出问题对话框&#xff0c;询问是否要确定退出登录&#xff0c;并提供两个按钮&#xff0c;yes|No&#xff0c;如果用户点击的Yes&#xff0c;则关闭对话框&#xff0c;如果用户点击的No&#xff0c;则继续登录 当用户点击的登录按钮&#xff…

六、Drf限流组件

六、限流组件 限制某个视图在某个时间段内被同一个用户访问的次数 6.1限流组件的简单应用 1&#xff09;安装django-redis pip3 install django-redis2)在settings.py中注册cache #缓存数据库redis配置 CACHES{"default":{"BACKEND":"django_red…