01
介绍
我们已经介绍过 Mutex、RWMutex 等并发原语操作,如果您还没有阅读,请查看文末「推荐阅读」列表。
本文我们介绍 sync/atomic 包提供的原子操作的方法,相比并发原语操作,使用原子操作会更轻量。
我们知道,相同代码在不同 CPU 架构中编译的结果可能不同,sync/atomic 包提供的原子操作的方法帮我们解决了这个问题,所以如果您想保证原子操作,一定要使用 sync/atomic 包提供的原子操作的方法。
02
方法
因为 Go (1.15)目前还没有支持泛型,所以每个 sync/atomic 包提供的原子操作的方法,都包含不同类型的同名方法。但是不同类型的同名方法除了类型不同,使用方法是相同的。
细心的读者可能发现,为什么 Add 方法只支持 5 种类型,其它方法都支持 6 种类型,因为在 Go 语言中,指针是不能运算的,Add 方法是修改操作,其它方法否是存取操作。限于篇幅,我们重点介绍 Add 方法。
Add 方法:
func AddInt32(addr *int32, delta int32) (new int32) func AddUint32(addr *uint32, delta uint32) (new uint32) func AddInt64(addr *int64, delta int64) (new int64) func AddUint64(addr *uint64, delta uint64) (new uint64) func AddUintptr(addr *uintptr, delta uintptr) (new uintptr
Add 方法的功能就是给参数 1 的地址中的值,加上参数 2 的 delta 的值。如果类型是有符号类型,参数 2 可以是负值,用于原子减法运算;如果类型是无符号类型,参数 2 不可以是负值,此时分两种情况:
- 情况 1 是参数 2 是一个无符号类型的变量 v,-v 在 GO 语言中是合法的。
- 情况 2 是参数 2 是一个正整数常量 c,-c 在 Go 语言中是非法的,此时我们可以使用 ^T(c-1) 作为参数 2 的值。
如果 ^T(c-1) 中的 c 是一个类型确定的值,可以简化为 ^(c-1)。
示例代码:
func main() { // 多个 goroutine 的原子计数器 var counter uint64 var wg sync.WaitGroup for i := 0; i < 50; i++ { wg.Add(1) go func() { for j := 0; j < 100000; j++ { // 相加 atomic.AddUint64(&counter, 1) } wg.Done() }() } wg.Wait() fmt.Println("counter:", counter) }
如果您感兴趣的话,可以将示例代码中第 10 行代码替换为 counter++,运行程序并查看运行结果是否会有不同。
Swap 方法:
func SwapInt32(addr *int32, new int32) (old int32) 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)
Swap 方法的功能是原值替换为新值,并返回原值。
CompareAndSwap 方法:
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)
CompareAndSwap 方法的功能是比较原值,符合条件则将原值替换为新值。
Load 方法:
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)
Load 方法的功能是取值。
Store 方法:
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)
Store 方法的功能是存值。
03
value 类型
细心的读者可能已经发现,上述 5 种方法,都是针对特定类型的方法。sync/atomic 还有一个 value 类型,它有两个方法,分别是 Load 和 Store,用于读取和存储任意类型的值,它没有 Add 和 Swap。
func (v *Value) Load() (x interface{}) func (v *Value) Store(x interface{})
Load 方法的返回结果类型和 Store 方法的参数类型都是空接口类型 interface{},所以 Store 方法的参数可以接收任意类型的值。
需要注意的是,*Value 类型的 v,只要调用过一次 Store 方法,那么传入此方法的后续参数类型必须和之前传入参数的类型一致,否则会导致 panic。
04
总结
本文主要介绍了 sync/atomic 包的原子操作的方法的基本使用,其中重点介绍了 Add 方法和 Value 类型的方法,读者朋友可以根据实际场景选用最合适的方法。