参数设计
func SwapInt32(addr *int32, new int32) (old int32)
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
func LoadInt32(addr *int32) (val int32)
原子操作函数的第一个参数值对应的都应该是那个被操作的值。比如,atomic.AddInt32函数的第一个参数,对应的一定是那个要被增大的整数。而且参数的类型都是要求是指针类型。*int32。
因为原子操作函数需要的是被操作值的指针,而不是这个值本身;被传入函数的参数值都会被复制,像这种基本类型的值一旦被传入函数,就已经与函数外的那个值毫无关系了所以,传入值本身没有任何意义。unsafe.Pointer类型虽然是指针类型,但是那些原子操作函数要操作的是这个指针值,而不是它指向的那个值,所以需要的仍然是指向这个指针值的指针。
只要原子操作函数拿到了被操作值的指针,就可以定位到存储该值的内存地址。只有这样,它们才能够通过底层的指令,准确地操作这个内存地址上的数据。
原子减法
atomic只提供了add相关的方法,并没有提供减法。但是atomic.AddInt32函数的第二个参数代表差量,它的类型是int32,是有符号的。如果我们想做原子减法,那么把这个差量设置为负整数就可以了。
无符号类型如:uint32可以下方法转化:
方法一:
uint32(int32(-N))
方法二:
^uint32(-N-1))
其中的N代表由负整数表示的差量。
交换与比较并交换
交换(swap)把新值赋给变量,并返回变量的旧值
比较并交换(compare and swap,简称CAS)是有条件的交换操作,只有在条件满足的情况下才会进行值的交换。在进行CAS操作的时候,函数会先判断被操作变量的当前值,是否与我们预期的旧值相等。如果相等,它就把新值赋给该变量,并返回true以表明交换操作已进行;否则就忽略交换操作,并返回false。
CAS操作并不是单一的操作,而是一种操作组合。应用要更广泛一些,如自旋锁
for {
if atomic.CompareAndSwapInt32(&num2, 10, 0) {
fmt.Println("The second number has gone to zero.")
break
}
time.Sleep(time.Millisecond * 500)
}
网上也有一些人用CAS做无锁编程所谓无锁编程只是将多条指令合并成了一条指令形成一个逻辑完备的最小单元,通过兼容CPU指令执行逻辑形成的一种多线程编程模型
载入与存储
载入Load 原子性的读取一个变量的值,确保在不会读到被修改到一半的值
存储Store原子地存储某个值的过程中,任何CPU都不会进行针对同一个值的读或写操作
思考
问题1:为什么不设计比较(compare)如果设计了比较(compare) ,在一段代码中, 我们先 比较(compare) 得出结果后再 交换(swap)。将CAS操作分成两步走还是原子是否安全?
答案肯定是不安全,compare 是原子的 swap 是原子,单不代表这两的组合是原子的。不设计compare原因应该是意义不大,CAS操作本身也可以实现compare,而且compare后往往还需跟其他的操作,swap通常是最尝被使用的。
问题 2:假设我已经保证了对一个变量的写操作都是原子操作,比如:加或减、存储、交换等等,那我对它进行读操作的时候,还有必要使用原子操作吗?
答案是很有必要其中的道理你可以对照一下读写锁。为什么在读写锁保护下的写操作和读操作之间是互斥的?这是为了防止读操作读到没有被修改完的值,对吗?
如果写操作还没有进行完,读操作就来读了,那么就只能读到仅修改了一部分的值。这显然破坏了值的完整性,读出来的值也是完全错误的。所以,一旦你决定了要对一个共享资源进行保护,那就要做到完全的保护。不完全的保护基本上与不保护没有什么区别