在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
参考: https://www.jianshu.com/p/6ca885ede2a8(sync/atomaic原子操作) https://zhuanlan.zhihu.com/p/401606797(知乎:atomic原子操作) 核心概念:原子性:一个或多个操作在CPU的执行过程中不被中断的特性,称为原子性。这些操作对外表现成一个不可分割的整体,他们要么都执行,要么都不执行,外界不会看到他们只执行到一半的状态。 原子操作:进行过程中不能被中断的操作,原子操作由底层硬件支持,而锁则是由操作系统提供的API实现,若实现相同的功能,前者通常会更有效率 协程并发问题在goroutine中访问外部的变量并不安全,我们先看看下面这个例子,我们执行一次计数,使用
sync.WaitGroup 包保证我们创建的1000个goroutine全部执行完毕后再输出n ,运行程序看看结果如何:func no_atomic() { var n int32 var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { n++ wg.Done() }() } wg.Wait() fmt.Println(atomic.LoadInt32(&n)) // 978 } 电脑多核的情况下,不限制cpu执行上面代码,每次执行的结果不是1000,而是小于等于一千数值波动。为什么会出现这种情况呢?上面我们通过for循环里创建1000个goroutine,每个goroutine都将n加上1,加入有其中两个goroutine如下执行步骤:
sync/atomic的原子操作,主要有五大类:Load:返回原值1,该类方法主要负责从相应的内存地址中获取对应的值 2,Load 方法是为了防止在读取过程中,有其他协程发起修改动作,影响了读取结果,常用于配置项的整个读取。 3,读取的时候,其他协程不能写入 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) Store:无返回值1,整体赋值 2,有原子读取,就有原子修改值,前面提到过的 Add 只适用于 int、uint 类型的增减,并没有其他类型的修改,而 Sotre 方法通过 unsafe.Pointer 指针原子修改,来达到了对其他类型的修改。 3,写入的时候,其他协程不能读取 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) Add:返回新值1,原子增减 2,可以理解为 先load,再+=,再store,三者中间不会打断 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) Swap:返回旧值1,原子赋值 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) CompareAndSwap:返回bool值1,该操作在进行交换前首先确保被操作数的值未被更改,即仍然保存着参数 old 所记录的值,满足此前提条件下才进行交换操作。 2,CAS的做法类似操作数据库时常见的乐观锁机制。 3,当有大量的goroutine 对变量进行读写操作时,可能导致CAS操作无法成功,这时可以利用for循环多次尝试。 4,CompareAndSwap 有可能产生 ABA 现象发生。也就是原来的值是 A,后面被修改 B,再后面修改为 A。在这种情况下也符合了 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)
atomic包中支持六种类型int32
uint32
int64
uint64
uintptr
unsafe.Pointer
对于每一种类型,提供了五类原子操作:LoadXXX(addr): 原子性的获取*addr的值,等价于: return *addr
StoreXXX(addr, val): 原子性的将val的值保存到*addr,等价于: addr = val AddXXX(addr, delta): 原子性的将delta的值添加到*addr并返回新值(unsafe.Pointer不支持),等价于: *addr += delta return *addr SwapXXX(addr, new) old: 原子性的将new的值保存到*addr并返回旧值,等价于: old = *addr *addr = new return old CompareAndSwapXXX(addr, old, new) bool: 原子性的比较*addr和old,如果相同则将new赋值给*addr并返回true,等价于: if *addr == old { *addr = new return true } return false 使用sync/atomic解决文首的问题,代码如下:func _atomic() {
var n int32
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
atomic.AddInt32(&n, 1)
wg.Done()
}()
}
wg.Wait()
fmt.Println(atomic.LoadInt32(&n)) // 1000
}
使用
atomic.AddInt32 进行加操作,最终输出预期的结果, 其实atomic包中的方法在执行完毕之前不会被其他的任务或者事件中断,该操作是并发安全,在向此地址写入或者读取值时原子性的操作,如果其他goroutine尝试读取或写入会被阻塞,直到此次原子操作完成。
各类方法的应用场景,在实际应用中发掘和体会
|
请发表评论