Golang学习笔记:原子操作

转载请注明出处:https://blog.csdn.net/sublio/article/details/106503276

全系列目录:https://blog.csdn.net/sublio/article/details/106480267

原子操作

目录

简介

类型

增或减Add

比较并交换CAS

载入Load

存储Store

交换Swap

原子值sync/atomic.Value


简介

  1. 执行过程中不能被中断的操作。在针对某个值的操作过程中,CPU绝不会再去执行其它针对该值的操作,无论这些其他操作是否是原子操作。

  2. 对于不能被取地址的值是无法进行原子操作的。

类型

  • int32

  • int64

  • uint32

  • uint64

  • uintptr

  • unsafe.Pointer

增或减Add

//把一个int32类型的变量i32的值增大3
newi32 := atomic.AddInt32(&i32, 3)
//类似函数还有:
//AddInt64
//AddUint32
//AddUint64
//AddUintptr
  1. 第一个参数是i32的地址。

  2. 第二个参数的类型与被操作数必须相同。

    1. AddUint32和AddUint64函数的第二个参数必须是uint32和画uint64,因此无法通过传递一个负数来减小被操作数,解决方案:

      //把uint32类型的变量的值增加NN(NN表示一个负整数),或者说减小-NN:
      atomic.AddUint32(&uint32, ^uint32(-NN-1))
      //把uint64类型的变量的值增加NN(NN表示一个负整数),或者说减小-NN:
      atomic.AddUint64(&uint64, ^uint64(-NN-1))
      //原因:
      //一个负整数的补码可以通过对它按位(除了符号位)求反码并加一得到。
      //一个负整数可以由对它的绝对值减1并求补码后得到数值的二进制表示法。
      //因此表达式uint32(int32(NN))和^uint32(-NN-1)的结果都是一样的。
      //比如-35都为11111111111111111111111111011101

       

  3. 函数会返回新值,但是实际上是赋值给另一个变量了,i32的值已经在函数返回之前被修改。

  4. unsafe.Pointer类型的值无法被加减。

比较并交换CAS

func CompareAndSwapInt32(addr *int32, old, new, int32) (swapped bool)
  1. CompareAndSwap为前缀的若干函数。

  2. 是否等于old,等于的话交换,否则不交换,只有交换了返回值才是true。

  3. 与锁的对比:

    1. 锁是悲观地觉得会有并发的操作修改被操作值,并需要使用锁将相关操作放入临界区加以保护,而CAS相当于乐观地假设原来的值没有被改变,并一旦确认这个假设的真实性就立即进行值替换。

    2. 优势:可以在不创建互斥量和临界区的情况下完成并发安全的值替换操作,可以大大减少同步对程序性能的损耗。

    3. 劣势:在被操作值被频繁变更的情况下,CAS的操作并那么不容易成功,有时候不得不利用for循环来进行多次尝试:

      var value int32
      func addValue(delta int32) {
      	for  {
      		v := value
      		if atomic.CompareAndSwapInt32(&value, v, (v + delta)) {
      			break
      		}
      	}
      }

       

  4. CAS操作不会让goroutine阻塞,但是可能使流程的执行暂时停滞,不过这种停滞大多十分短暂。

  5. 想要并发安全地更新一些类型时,应优先选择CAS。

  6. 上述所有类型(6种)都支持CAS操作,包括CompareAndSwapPointer。

 

载入Load

  1. 原子地读取某个值(当前计算机中的任何CPU都不会进行其它的针对此值的读写操作)。

    func AddValue(delta int32) {
    	for  {
        //原子地读取value的值并赋值给v。
    		v := atomic.LoadInt32(&value)
        //在上面的赋值语句以及下面的if语句不会原子地执行。
        //因此加上下面的CAS是很有必要的。
    		if atomic.CompareAndSwapInt32(&value, v, (v + delta)) {
    			break
    		}
    	}
    }

     

  2. 上述所有类型(6种)都支持载入操作,包括LoadPointer。

 

存储Store

  1. 原子地写入某个值(当前计算机中的任何CPU都不会进行其它的针对此值的读写操作)。

  2. 与CAS的区别:不关心原来的值是啥。

 

交换Swap

  1. 两个参数:被操作数的地址,新值。

  2. 与CAS区别:不关心被操作数的旧值,但是会把旧的值返回回来。

  3. 比CAS约束少,比载入功能更强。

 

原子值sync/atomic.Value

  1. 该类型有两个公开的指针方法:Load和Store。

  2. 存储Store:

    1. 接收一个interface{}类型的参数。

    2. 限制:不能把nil作为参数传入;第二次即以后传入的值必须与之前传入值的类型一致。违反了这两个限制会导致一个运行时恐慌。

  3. 读取Load:

    1. 返回一个interface{}类型的结果。

    2. 如果还没有Store那么返回的会是nil。

  4. sync/atomic.Value类型的变量一旦被声明,就不应该被复制到别的地方,虽然不会造成编译错误,但是go vet会报告此类不正确,原因是:对结构体的复制会生成该值的副本,还会生成其中字段的副本,这样一来,并发安全保护也就失效惹。甚至向副本存储值的操作与原值都无关了,当然,sync/atomic.Value类型的指针类型的变量不存在这个问题。如果Value里面存储的值是引用类型,那么更要注意了。

    //以下程序输出是:[4 5 6]
    func main() {
    	var a atomic.Value
    	a.Store([]int{4,5,6})
    	anotherStore(a)
    	fmt.Println(a.Load())
    }
    func anotherStore(a atomic.Value)  {
    	a.Store([]int{1,2,3})
    }
    //以下程序输出是:[3 5 6]
    func main() {
    	var a atomic.Value
    	a.Store([]int{4,5,6})
    	anotherStore(a)
    	fmt.Println(a.Load())
    }
    func anotherStore(a atomic.Value)  {
    	b := a.Load().([]int)
    	b[0] = 3
    //以下程序输出是:[4 5 6]
    func main() {
    	var a atomic.Value
    	a.Store([]int{4,5,6})
    	anotherStore(a)
    	fmt.Println(a.Load())
    }
    
    func anotherStore(a atomic.Value)  {
    	a.Store([]int{1,2,3})
    	b := a.Load().([]int)
    	b[0] = 3
    }

     

  5. 可能导致复制的操作如下:

    1. 赋值给别人。

    2. 作为函数参数传入。

    3. 作为函数结果返回。

    4. 作为元素值通过通道传递。

参考文献

《Go并发编程实战(第2版)》——郝林

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值