Go语言——原子操作

Go语言——原子操作

参考:

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

Background

原子操作即执行过程不能被中断的操作(并发)。

经典问题:i++是不是原子操作?

答案是否,因为i++看上去只有一行,但是背后包括了多个操作:取值,加法,赋值。

加/减

atomic.AddInt32(&i, 1)

代码很好理解,原子地对i加1。

问题是:为什么加法需要原子性?

思考,在32位OS,操作long类型,是否是原子的?

答案是否,因为32位操作系统CPU一次只能操作32位数据,如果两个64位相加,那么首先会计算低32位,然后计算高32位。

除了操作系统原因,我觉得还有一个原因,因为仅仅i+1并不能对i加1,还必须赋值。

CAS

Compare And Swap,比较并交换,常见的原子操作。

func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)

三个入参分别表示:被操作值的指针,被操作值的old值,被操作值的new值。

首先会判断指针指向的被操作值是否与old相等,如果相等就用new替换old,完成数据更新;否则忽略替换操作。整个过程都是原子性的。

Note:CAS与lock区别:

  1. CAS不需要创建互斥量,临界区,所以性能好于lock,这也是性能优化的一个点;
  2. CAS是乐观的,即认为old没有被并发修改,而lock是悲观的,总是认为old是并发不安全的;
  3. 如果old被并发修改了,CAS就不会成功。所以我们需要循环多次执行CAS操作,以让他生效,类似自旋。

Load

原子读取变量,防止变量值其他并发读写操作。

还是之前操作系统的例子,在32位操作系统,写入一个long,如果在这个写操作完成前(先改低32位,再改高32位),有一个并发读操作,这个读操作就可能会读取一个只被修改一般的数据。

Store

例子与之前类似。原子存储操作有一个特点,与CAS不同,存储总是成功的,它不关心old。

func StoreInt32(addr *int32, val int32)

Swap

与CAS不同,他不关心old;与Store不同,它会返回old。介于两者之间。

func SwapInt32(addr *int32, new int32) (old int32)

原子值

上面的原子操作都只支持基本类型,如果想对其他类型进行原子操作怎么办?所以Go提供了原子值类型,它对于类型没有限制。它支持Load和Store方法。

// A Value provides an atomic load and store of a consistently typed value.
// The zero value for a Value returns nil from Load.
// Once Store has been called, a Value must not be copied.
//
// A Value must not be copied after first use.
type Value struct {
    v interface{}
}

执行Store之后,Value禁止copy。目前已知的copy行为包括:赋值其他变量,函数入参,函数返回值,传入chan。

// Store sets the value of the Value to x.
// All calls to Store for a given Value must use values of the same concrete type.
// Store of an inconsistent type panics, as does Store(nil).
func (v *Value) Store(x interface{}) {
    if x == nil {
        panic("sync/atomic: store of nil value into Value")
    }
    vp := (*ifaceWords)(unsafe.Pointer(v))
    xp := (*ifaceWords)(unsafe.Pointer(&x))
    for {
        typ := LoadPointer(&vp.typ)
        if typ == nil {
            // Attempt to start first store.
            // Disable preemption so that other goroutines can use
            // active spin wait to wait for completion; and so that
            // GC does not see the fake type accidentally.
            runtime_procPin()
            if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
                runtime_procUnpin()
                continue
            }
            // Complete first store.
            StorePointer(&vp.data, xp.data)
            StorePointer(&vp.typ, xp.typ)
            runtime_procUnpin()
            return
        }
        if uintptr(typ) == ^uintptr(0) {
            // First store in progress. Wait.
            // Since we disable preemption around the first store,
            // we can wait with active spinning.
            continue
        }
        // First store completed. Check type and overwrite data.
        if typ != xp.typ {
            panic("sync/atomic: store of inconsistently typed value into Value")
        }
        StorePointer(&vp.data, xp.data)
        return
    }
}
}

store不允许nil,并且类型必须与第一次store一致,否则panic。

    原文作者:陈先生_9e91
    原文地址: https://www.jianshu.com/p/ccfbe7bf82bb
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞