Go 原子操作

大二小的宝 · · 1486 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

概念

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

有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:大二小的宝

查看原文:Go 原子操作

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1486 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传