持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情
🎐 放在前面说的话
大家好,我是沥沥樱 👧🏻
本科在读,此为日常捣鼓.
如有不对,请多指教,也欢迎大家来跟我讨论鸭 👏👏👏
还有还有还有很重要的,麻烦大可爱们动动小手,给沥沥樱点颗心心♥,沥沥樱需要鼓励嗷呜~
今天我们来看一下原子操作5种姿势
Let’s get it!
原子操作概述
基本概念
原子性:一个或多个操作在CPU的执行过程中不被中断的特性,称为原子性。这些操作对外表现成一个不可分割的整体,他们要么都执行,要么都不执行,外界不会看到他们只执行到一半的状态。
原子操作:进行过程中不能被中断的操作
原子操作&锁比较
- 原子操作由底层硬件支持,而锁则是由操作系统提供的API实现,原子操作比锁更为高效。
- 加锁比较耗时,需要上下文切换。即使是goroutine也需要上下文切换
- 只针对基本类型,可使用原子操作保证线程安全
- 原子操作在用户态完成,性能比互斥锁要高
- 原子操作步骤简单,不需要加锁-操作-解锁
五种操作函数
- 增或减 (Add)
- 比较并交换 (CAS, Compare And Swap)
- 载入 (Load)
- 存储 (Store)
- 交换 (Swap)
增或减(Add)
函数名称都以Add为前缀,并后跟针对的具体类型的名称。
- 被操作的类型只能是数值类型
- int32,int64,uint32,uint64,uintptr类型可以使用原子增或减操作
- 第一个参数值必须是一个指针类型的值,以便施加特殊的CPU指令
- 第二个参数值的类型和第一个被操作值的类型总是相同的。
func main() {
var i32 int32
fmt.Println("=====old i32 value=====")
fmt.Println(i32)
//第一个参数值必须是一个指针类型的值,因为该函数需要获得被操作值在内存中的存放位置,以便施加特殊的CPU指令
//结束时会返回原子操作后的新值
for i := 0; i < 3; i++ {
newI32 := atomic.AddInt32(&i32, 1)
//newI32 := atomic.AddInt64(&i32, -1)
fmt.Println("=====new i32 value=====")
fmt.Println(i32)
fmt.Println(newI32)
}
}
打印:
=====old i32 value=====
0
=====new i32 value=====
1
1
=====new i32 value=====
2
2
=====new i32 value=====
3
3
比较并交换 (CAS, Compare & Swap)
以‘CompareAndSwap’为前缀的若干个函数代表。
- 声明如下
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
- 仍然保持参数
old
所记录的值,满足此前提下才进行交换操作,否则操作就会被忽略。 - 当有大量 goroutine 对变量进行读写操作时,可能导致CAS操作无法成功,需要用for循环不断进行尝试,直到成功为止
var value int32
func main() {
fmt.Println("======old value=======")
fmt.Println(value)
fmt.Println("======CAS value=======")
addValue(2)
fmt.Println(value)
}
// 不断地尝试原子地更新value的值,直到操作成功为止
func addValue(delta int32) {
// 操作值被频繁变更的情况下,CAS操作不容易成功,需要for循环以进行多次尝试
for {
v := value
if atomic.CompareAndSwapInt32(&value, v, (v + delta)) {
//在函数的结果值为true时,退出循环
break
}
//操作失败的缘由总会是value的旧值已不与v的值相等了.
}
}
打印:
======old value=======
0
======CAS value=======
2
载入 (Load)
上面的比较并交换案例总 v:= value为变量v赋值,但… 要注意,在进行读取value的操作的过程中,其他对此值的读写操作是可以被同时进行的,那么这个读操作很可能会读取到一个只被修改了一半的数据。所以,我们需要使用Load为前缀(载入),来确保这样的糟糕事情发生。
- atomic.LoadInt32接受一个*int32类型的指针值
- 返回该指针指向的那个值
var value int32
func main() {
fmt.Println("======old value=======")
fmt.Println(value)
fmt.Println("======CAS value=======")
addValue(2)
fmt.Println(value)
}
//不断地尝试原子地更新value的值,直到操作成功为止
func addValue(delta int32) {
// 操作值被频繁变更的情况下,CAS操作不容易成功,需要for循环以进行多次尝试
for {
//v := value
// 使用载入防止读取到一个只被修改了一半的数据
v := atomic.LoadInt32(&value)
if atomic.CompareAndSwapInt32(&value, v, (v + delta)) {
//在函数的结果值为true时,退出循环
break
}
//操作失败的缘由总会是value的旧值已不与v的值相等了.
}
}
打印同上
存储 (Store)
与原子的载入函数相对应的原子的值存储函数。,以Store为前缀
- 在原子地存储某个值的过程中,任何CPU都不会进行针对同一个值的读或写操作。
- 原子的值存储操作总会成功,因为它并不会关心被操作值的旧值是什么
- 和CAS操作有着明显的区别
var value int32
func main() {
fmt.Println("======old value=======")
fmt.Println(value)
fmt.Println("======Store value=======")
atomic.StoreInt32(&value, 1)
fmt.Println(value)
}
打印:
======old value=======
0
======Store value=======
1
交换 (Swap)
- 与CAS操作不同,原子交换操作不会关心被操作的旧值。
- 它会直接设置新值
- 它会返回被操作值的旧值
- 此类操作比CAS操作的约束更少,同时又比原子载入操作的功能更强
🎉 放在后面说的话
以上简单总结了Go并发中原子操作增或减 (Add)、比较并交换 (CAS, Compare And Swap)、载入 (Load)、存储 (Store)、交换 (Swap)的使用,其他后期补充