30

Go 原子操作

 5 years ago
source link: https://studygolang.com/articles/19638?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

概念

原子操作,意思就是执行的过程不能背终端的操作。在针对某个值的原子操作执行过程中,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
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK