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区别:
- CAS不需要创建互斥量,临界区,所以性能好于lock,这也是性能优化的一个点;
- CAS是乐观的,即认为old没有被并发修改,而lock是悲观的,总是认为old是并发不安全的;
- 如果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。