转载请注明出处:https://blog.csdn.net/sublio/article/details/106503276
全系列目录:https://blog.csdn.net/sublio/article/details/106480267
原子操作
目录
简介
-
执行过程中不能被中断的操作。在针对某个值的操作过程中,CPU绝不会再去执行其它针对该值的操作,无论这些其他操作是否是原子操作。
-
对于不能被取地址的值是无法进行原子操作的。
类型
-
int32
-
int64
-
uint32
-
uint64
-
uintptr
-
unsafe.Pointer
增或减Add
//把一个int32类型的变量i32的值增大3
newi32 := atomic.AddInt32(&i32, 3)
//类似函数还有:
//AddInt64
//AddUint32
//AddUint64
//AddUintptr
-
第一个参数是i32的地址。
-
第二个参数的类型与被操作数必须相同。
-
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
-
-
函数会返回新值,但是实际上是赋值给另一个变量了,i32的值已经在函数返回之前被修改。
-
unsafe.Pointer类型的值无法被加减。
比较并交换CAS
func CompareAndSwapInt32(addr *int32, old, new, int32) (swapped bool)
-
CompareAndSwap为前缀的若干函数。
-
是否等于old,等于的话交换,否则不交换,只有交换了返回值才是true。
-
与锁的对比:
-
锁是悲观地觉得会有并发的操作修改被操作值,并需要使用锁将相关操作放入临界区加以保护,而CAS相当于乐观地假设原来的值没有被改变,并一旦确认这个假设的真实性就立即进行值替换。
-
优势:可以在不创建互斥量和临界区的情况下完成并发安全的值替换操作,可以大大减少同步对程序性能的损耗。
-
劣势:在被操作值被频繁变更的情况下,CAS的操作并那么不容易成功,有时候不得不利用for循环来进行多次尝试:
var value int32 func addValue(delta int32) { for { v := value if atomic.CompareAndSwapInt32(&value, v, (v + delta)) { break } } }
-
-
CAS操作不会让goroutine阻塞,但是可能使流程的执行暂时停滞,不过这种停滞大多十分短暂。
-
想要并发安全地更新一些类型时,应优先选择CAS。
-
上述所有类型(6种)都支持CAS操作,包括CompareAndSwapPointer。
载入Load
-
原子地读取某个值(当前计算机中的任何CPU都不会进行其它的针对此值的读写操作)。
func AddValue(delta int32) { for { //原子地读取value的值并赋值给v。 v := atomic.LoadInt32(&value) //在上面的赋值语句以及下面的if语句不会原子地执行。 //因此加上下面的CAS是很有必要的。 if atomic.CompareAndSwapInt32(&value, v, (v + delta)) { break } } }
-
上述所有类型(6种)都支持载入操作,包括LoadPointer。
存储Store
-
原子地写入某个值(当前计算机中的任何CPU都不会进行其它的针对此值的读写操作)。
-
与CAS的区别:不关心原来的值是啥。
交换Swap
-
两个参数:被操作数的地址,新值。
-
与CAS区别:不关心被操作数的旧值,但是会把旧的值返回回来。
-
比CAS约束少,比载入功能更强。
原子值sync/atomic.Value
-
该类型有两个公开的指针方法:Load和Store。
-
存储Store:
-
接收一个interface{}类型的参数。
-
限制:不能把nil作为参数传入;第二次即以后传入的值必须与之前传入值的类型一致。违反了这两个限制会导致一个运行时恐慌。
-
-
读取Load:
-
返回一个interface{}类型的结果。
-
如果还没有Store那么返回的会是nil。
-
-
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 }
-
可能导致复制的操作如下:
-
赋值给别人。
-
作为函数参数传入。
-
作为函数结果返回。
-
作为元素值通过通道传递。
-
参考文献
《Go并发编程实战(第2版)》——郝林