共计 2420 个字符,预计需要花费 7 分钟才能阅读完成。
概念
原子操作,意思就是执行的过程不能背终端的操作。在针对某个值的原子操作执行过程中,cpu 不会再去执行其他针对这个值得操作。在底层,这会由 CPU 提供芯片级别的支持,所以绝对有效。即使在拥有多 CPU 核心,或者多 CPU 的计算机系统中,原子操作的保证也是不可撼动的。Go 语言提供了院子操作的包 atomic。其中有很多函数可以帮助我们进行原子操作。但是只能对几种简单类型进行原子操作:int32、int64、uint32、uint64、uintptr 和 unsafe.Ponter。atomic 为这些简单类型童工了 5 中操作函数:增或减、比较并交换、载入、存储和交换。
为什么选择原子操作
我们知道 go 语言在 sync 包中提供了锁的包,但是为什么我们还要使用 atomic 原子操作呢?总结下来有一下几个原因:
加锁的代价比较耗时,需要上下文切换。即使是在 go 语言的 goroutine 中也需要上下文的切换
只是针对基本类型,可以使用原子操作保证线程安全
原子操作在用户态可以完成,性能比互斥锁要高
针对特定需求,原子操作的步骤简单,不需要加锁 - 操作 - 解锁 这样的步骤
五种操作
一下 5 中操作例子都是用 uint64 来写
增或减
针对以上 6 种简单类型,atomic 包支持院子增 / 减的操作函数。
var i64 uint64
// 第一个参数必须是指针
atomic.AddUint64(&i64,5)
// 在 uint 类型中可以使用 ^uint64(0) 的方式打到减的效果
atomic.AddUint64(&i64, ^uint64(0))
fmt.Println(i64)
比较并交换(CAS-Compare And Swap)
var i64 uint64
i64 = 5
// cas 接受 3 个参数,第一个为需要替换值得指针,第二个为旧值,第三个为新值
// 当指针指向的值,跟你传递的旧值相等的情况下 指针指向的值会被替换
ok := atomic.CompareAndSwapUint64(&i64,5, 50)
fmt.Println(ok)
// 当指针指向的值跟传递的旧值不相等,则返回 false
ok = atomic.CompareAndSwapUint64(&i64,40, 50)
fmt.Println(ok)
载入
var i64 uint64
i64 = 1
//load 函数接收一个指针类型 返回指针指向的值
num := atomic.LoadUint64(&i64)
fmt.Println(num)
存储
var i64 uint64
i64 = 1
//store 函数接受一个指针类型和一个值 函数将会把值赋到指针地址中
atomic.StoreUint64(&i64, 5)
fmt.Println(i64)
交换
var i64 uint64
i64 = 1
//swap 接受一个指针 一个值。函数会把值赋给指针 并返回旧值
old := atomic.SwapUint64(&i64, 5)
fmt.Println(“old:”,old,”new:”,i64)
原子值
原子值可接受的备操作值得类型不限,这意味着我们可以把任何类型的值放入原子值。原子值只有 2 个公开的方法:Load、Store。一个是获取另一个是存储。下面来看下简单操作:
var countVal atomic.Value
//store 函数 接受 interface 并存储
countVal.Store([]int{1,2,3,4,5})
//load 函数 返回 atomic.value 中的值
list := countVal.Load().([]int)
fmt.Println(list)
下面有一个并发安全的 int 数组的例子
package main
import (
“errors”
“sync/atomic”
)
func main() {
}
// ConcurrentArray 代表并发安全的整数数组接口。
type ConcurrentArray interface {
// Set 用于设置指定索引上的元素值。
Set(index uint32, elem int) (err error)
// Get 用于获取指定索引上的元素值。
Get(index uint32) (elem int, err error)
// Len 用于获取数组的长度。
Len() uint32
}
type MyArray struct {
val atomic.Value
length uint32
}
func (array *MyArray)CheckValue()(err error){
if array.val.Load() == nil{
errors.New(“array is empty”)
}
return nil
}
func (array *MyArray)CheckIndex(index uint32)(error){
if array.length <= index{
errors.New(“array out of the range”)
}
return nil
}
func (m *MyArray)Set(index uint32, elem int)(err error){
if err := m.CheckValue();err != nil{
return err
}
if err = m.CheckIndex(index);err!=nil{
return err
}
newArray := make([]int, m.length)
copy(newArray ,m.val.Load().([]int))
newArray[index] = elem
m.val.Store(newArray)
return nil
}
func (array *MyArray)Get(index uint32) (elem int, err error){
if err := array.CheckValue();err != nil{
return 0,err
}
if err = array.CheckIndex(index);err!=nil{
return 0,err
}
num := array.val.Load().([]int)[index]
return num, err
}