golang实现原子操作的几种方式
什么是原子操作?
原子操作就是并发编程中“最小的且不可并行化”的操作。如果有多个并发体对同一个共享资源进行的操作是原子的话,那么同一时刻最多只能有一个并发体对该资源进行操作。
golang 中实现原子操作的几种方式
内置包 sync/atomic
sync/atomic 包提供的原子操作有几大类:
增减,操作方法有: AddInt32, AddInt64, AddUint32, AddUint64, AddUintptr.
载入,操作方法有: LoadInt32,LoadInt64,LoadUint32, LoadUnt64, LoadPointer, LoadUintptr。
存储,载入对应的方法为存储,这列操作的方法名是以Store开头
比较并交换, 也就是CAS(Compare and Swap)
代码示例:
1 | func main() { |
AddUint32 减的特殊操作
方法一: 通过声明变量,转成uint类型
示例代码如下:
1 | var sub int32 = -1 |
方法二:通过补码的方式实现 n 为负数 要减的值 公式为^(-n-1)
1 | atomic.AddUint32(&count, ^uint32(0)) |
比较与交换(CAS)
1 | atomic.CompareAndSwapInt32() |
该操纵简称CAS(Compare and Swap)。这类操作的前缀为CompareAndSwap。
该操作在进行交换前首先确保被操作数的值未被更改,即仍然保存着 参数old所记录的值,满足此前提条件下才进行交换操作。CAS的做法蕾丝操作数据库时常见的乐观锁机制。
需要注意到是,当大量的goroutine对变量进行读写操作时,可能导致CAS操作无法成功,这时可以利用for循环多次尝试。
atomic.Value 保证任意值的读写安全
atomic 包里提供了一套store 开头的方法,用来保证各种类型变量的并发写安全,避免其他操作读到了修改变量过程中的藏数据。
1 | atomic.StoreInt32() |
如果想并发安全的设置一个结构体的多个字段,可以通过atomic.Value实现。
atomic.Value对外暴露了两个方法:
- v.Store(c) - 写操作,将原始的变量c存放到一个atomic.Value类型的v里。
- c := v.Load() - 读操作,从线程安全的v中读取上一步中存放的内容。
由于 Load 返回的是一个interface{} 类型,所以在使用前需要先转换成具体类型的值,再使用。
示例代码
1 | var rect atomic.Value |
互斥锁
互斥锁实现原子操作
1 | func main() { |
互斥锁于原子锁的区别
- 使用目的: 互斥锁是用来保护一段逻辑,原子操作用于对一个变量的更新和保护
- 底层实现:Mutex 由操作系统的调度器实现,而atomic包中的原子操作则有底层硬件指令提供支持,这些指令在执行过程中是不允许中断的,因此原子操作可以 lockk-free的情况下保证并发安全,并且他的性能也能随cpu个数的增多而现行扩展。
对于一个变量更新的保护,原子操作通常会更具有效率,并且能利用计算机多核的优势。