Golang语言标准库之 sync/atomic原子操作
原子操作,顾名思义是不可分割的,他可以是一个步骤,也可以是多个步骤,其执行过程不会被线程调度机制打断的操作。
原子性不可能由软件单独保证,需要硬件的支持,因此和架构有关。在x86架构平台下,cpu提供了在指令执行期间对总线加锁的手段。
CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,
持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。
sync/atomic包的文件结构以及数据结构可以参考这里
sync/atomic包提供了6中操作数据类型
- int32
- uint32
- int64
- uint64
- uintptr
- unsafe.Pointer
分别为这每种数据类型提供了五种操作
- add 增减
- load 载入
- store 存储
- compareandswap 比较并交换
- swap 交换
下面以int32为例,具体使用上面五种操作实现原子操作
AddInt32操作
var val int32
val = 10
atomic.AddInt32(&val, 10)
// 对于无符号32位即uint32,则需要使用二进制补码进行操作
var val2 uint32
val2 = 10
atomic.AddUint32(&val2, ^uint32(10 - 1)) // 等价于 val2 - 10
CompareAndSwapInt32
对比并交换是指先判断addr指向的值是否与参数old一致,如果一致就用new值替换addr的值,最后返回成功,具体例子如下
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var val int32
wg := sync.WaitGroup{}
//开启100个goroutine
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
old := atomic.LoadInt32(&val)
if !atomic.CompareAndSwapInt32(&val, old, old+1) {
fmt.Println("修改失败")
}
}()
}
wg.Wait()
//val的值有可能不等于100,频繁修改变量值情况下,CompareAndSwap操作有可能不成功。
fmt.Println("c : ", val)
}
SwapInt32
进行赋值操作,然后返回旧值
var val int32
buf := atomic.LoadInt32(&val)
old := atomic.SwapInt32(&val, buf + 1)
LoadInt32
载入一个int32的值,只保证读取的时候是原子的,即不是正在写入的值
StoreInt32
向存储地址写入指定值,保证存储的时候不会被读写操作,即保证写入的原子性