go 原子操作
Contents
go 原子操作
原子操作
像Java一样,Golang支持很多CAS操作。运行结果是unsaftCnt可能小于200,因为unsafeCnt++在机器指令层面上不是一条指令,而可能是从内存加载数据到寄存器、执行自增运算、保存寄存器中计算结果到内存这三部分,所以不进行保护的话有些更新是会丢失的。
package main
import (
“fmt”
“time”
“sync/atomic”
“runtime”
)
func main() {
// IMPORTANT!!!
runtime.GOMAXPROCS(4)
// thread-unsafe
var unsafeCnt int32 = 0
for i := 0; i < 10; i++ {
go func() {
for i := 0; i < 20; i++ {
time.Sleep(time.Millisecond)
unsafeCnt++
}
}()
}
time.Sleep(time.Second)
fmt.Println("cnt: ", unsafeCnt)
// CAS toolkit
var cnt int32 = 0
for i := 0; i < 10; i++ {
go func() {
for i := 0; i < 20; i++ {
time.Sleep(time.Millisecond)
atomic.AddInt32(&cnt, 1)
}
}()
}
time.Sleep(time.Second)
cntFinal := atomic.LoadInt32(&cnt)
fmt.Println("cnt: ", cntFinal)
}
神奇CAS的原理
Golang的AddInt32()类似于Java中AtomicInteger.incrementAndGet(),其伪代码可以表示如下。二者的基本思想是一致的,本质上是 乐观锁: 首先,从内存位置M加载要修改的数据到寄存器A中;然后,修改数据并保存到另一寄存器B;最终,利用CPU提供的CAS指令 (Java通过JNI调用到) 用一条指令完成: 1) A值与M处的原值比较;2) 若相同则将B值覆盖到M处。
若不相同,则CAS指令会失败,说明从内存加载到执行CAS指令这一小段时间内,发生了上下文切换,执行了其他线程的代码修改了M处的变量值。那么重新执行前面几个步骤再次尝试。
ABA问题: 即另一线程修改了M位置的数据,但是从原值改为C,又从C改回原值。这样上下文切换回来,CAS指令发现M处的值"未改变” (实际是改了两次,最后改回来了) ,所以CAS指令正常执行,不会失败。这种问题在Java中可以用AtomicStampedReference/AtomicMarkableReference解决。
Author -
LastMod 2020-04-26