import "sync/atomic"
atomic包提供了底层的原子级内存操作,对于同步算法的实现很有用。
这些函数必须谨慎地保证正确使用。除了某些特殊的底层应用,使用通道或者sync包的函数/类型实现同步更好。
应通过通信来共享内存,而不通过共享内存实现通信。
相比于使用锁,在条件允许的情况下,使用原子操作的效率会更高
atomic包能对一些类型进行原子操作,包括int32, int64, uint32, uint64, uintptr, unsafe.Pointer六种类型。原子操作包括增或减、交换、比较并交换、载入、存储五种操作。
一、载入
载入包含以下六个函数,分别是对上述六种数据类型的操作。
func LoadInt32(addr *int32) (val int32) func LoadInt64(addr *int64) (val int64) func LoadUint32(addr *uint32) (val uint32) func LoadUint64(addr *uint64) (val uint64) func LoadUintptr(addr *uintptr) (val uintptr) func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
载入函数是使我们能够原子性的获取*addr的值,代表变量的原子性读。变量的读可能在一个时钟周期内无法完成,无法保证并发安全。
二、存储
存储包含以下六个函数,对应六种数据类型。
func StoreInt32(addr *int32, val int32) func StoreInt64(addr *int64, val int64) func StoreUint32(addr *uint32, val uint32) func StoreUint64(addr *uint64, val uint64) func StoreUintptr(addr *uintptr, val uintptr) func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
存储函数是使我们能够原子性的将val值保存到*addr中。代表变量的原子性写。变量的写可能在一个时钟周期内无法完成,无法保证并发安全。
三、增或减
存储包含以下五个函数,对应五种数据类型。
func AddInt32(addr *int32, delta int32) (new int32) func AddInt64(addr *int64, delta int64) (new int64) func AddUint32(addr *uint32, delta uint32) (new uint32) func AddUint64(addr *uint64, delta uint64) (new uint64) func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)
增减函数是使我们能够原子性的将val值添加到*addr中,并返回这个新值。如下:
func main() { var a int32 var b uint32 a += 10 atomic.AddInt32(&a, -10) fmt.Println(a) // a=0 b += 20 atomic.AddIne32(&b, ^uint32(10-1)) fmt.Println(b) // b=10 )
尽管名字是Add,但是也可以实现减操作,特别注意uint无符号类型的减法操作。
四、交换
存储包含以下五个函数,对应五种数据类型。
func SwapInt64(addr *int64, new int64) (old int64) func SwapUint32(addr *uint32, new uint32) (old uint32) func SwapUint64(addr *uint64, new uint64) (old uint64) func SwapUintptr(addr *uintptr, new uintptr) (old uintptr) func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)
交换函数是使我们能够原子性的将val值保存到*addr中,并返回addr的旧值。原子操作不会关心旧值,只是单纯的返回。
五、比较并交换
存储包含以下六个函数,对应六种数据类型。
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool) func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool) func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool) func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool) func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool) func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
比较交换函数是使我们能够原子性的比较*addr和old,如果相同则将new赋值给*addr并返回真。如下:
var value int32 func main() { fmt.Println("旧值: ", value) addValue(3) fmt.Println("CAS操作: ", value) } //不断地尝试原子地更新value的值,直到操作成功为止 func addValue(delta int32){ //在被操作值被频繁变更的情况下,CAS操作并不那么容易成功 //so 不得不利用for循环以进行多次尝试 for { v := value if atomic.CompareAndSwapInt32(&value, v, (v + delta)){ //在函数的结果值为true时,退出循环 break } //操作失败的缘由总会是value的旧值已不与v的值相等了. //CAS操作虽然不会让某个Goroutine阻塞在某条语句上,但是仍可能会使流产的执行暂时停一下,不过时间大都极其短暂. } }
参考资料:
1. https://studygolang.com/pkgdoc
2. https://studygolang.com/articles/3557
记录每天解决的小问题,积累起来去解决大问题。