学习atomic包时,发现一个有趣的东西:
- unsafe.Pointer类型,该包并未提供进行原子加法操作的函数。
- uint*类型怎么做减法运算?
我们来看第2点:
var a int64 = 10
atomic.AddInt64(&a,-3)
fmt.Println(a)
结果很明显是:7
但是当我们对uint*进行减法运算时:
var b uint64 = 10
atomic.AddUint64(&b,-3)
fmt.Println(b)
直接这样写会使 Go 语言的编译器报错,它会告诉你:“常量-3不在uint64类型可表示的范围内”,换句话说,这样做会让表达式的结果值溢出。
.\reflect_test.go:68:22: constant -3 overflows uint64
不过,如果我们先把int64(-3)的结果值赋给变量c,再把c的值转换为uint64类型的值,就可以绕过编译器的检查并得到正确的结果了。最后,我们把这个结果作为atomic.AddUint64函数的第二个参数值,就可以达到对uint64类型的值做原子减法的目的了。
var b uint64 = 10
c := int64(-3)
d := uint64(c)
atomic.AddUint64(&b,d)
fmt.Println(b)
结果就是正确的:7
当然,还有一种更巧妙的方法:
我们可以依据下面这个表达式来给定atomic.AddUint64函数的第二个参数值:^uint64(-N-1))
其中的N代表由负整数表示的差量。也就是说,我们先要把差量的绝对值减去1,然后再把得到的这个无类型的整数常量,转换为uint64类型的值,最后,在这个值之上做按位异或操作,就可以获得最终的参数值了。
var b uint64 = 10
var e int64 = -3
atomic.AddUint64(&b,^uint64(-e-1))
fmt.Println(a,b)
结果:7
是不是很巧妙,这么做的原理也并不复杂。简单来说,此表达式的结果值的补码,与使用前一种方法得到的值的补码相同,所以这两种方式是等价的。我们都知道,整数在计算机中是以补码的形式存在的,所以在这里,结果值的补码相同就意味着表达式的等价。