golang实现原子操作的几种方式

什么是原子操作?

原子操作就是并发编程中“最小的且不可并行化”的操作。如果有多个并发体对同一个共享资源进行的操作是原子的话,那么同一时刻最多只能有一个并发体对该资源进行操作。

golang 中实现原子操作的几种方式

内置包 sync/atomic

sync/atomic 包提供的原子操作有几大类:

  • 增减,操作方法有: AddInt32, AddInt64, AddUint32, AddUint64, AddUintptr.

  • 载入,操作方法有: LoadInt32,LoadInt64,LoadUint32, LoadUnt64, LoadPointer, LoadUintptr。

  • 存储,载入对应的方法为存储,这列操作的方法名是以Store开头

  • 比较并交换, 也就是CAS(Compare and Swap)

代码示例:

1
2
3
4
5
6
7
8
9
func main()  {
var operate int32 = 1
atomic.AddInt32(&operate, 1)
fmt.Printf("o = %v\n", operate)
atomic.CompareAndSwapInt32(&operate, 2, 3)
fmt.Printf("o = %v\n", operate)
v := atomic.LoadInt32(&operate)
fmt.Printf("o = %v\n", v)
}

AddUint32 减的特殊操作

方法一: 通过声明变量,转成uint类型

示例代码如下:

1
2
3
var sub int32 = -1
atomic.AddUint32(&count, uint32(sub))
fmt.Println("count = ", count)
方法二:通过补码的方式实现 n 为负数 要减的值 公式为^(-n-1)
1
2
atomic.AddUint32(&count, ^uint32(0))
fmt.Println("count = ", count)

比较与交换(CAS)

1
2
3
4
5
6
atomic.CompareAndSwapInt32()
atomic.CompareAndSwapInt64()
atomic.CompareAndSwapUint32()
atomic.CompareAndSwapUint64()
atomic.CompareAndSwapPointer()
atomic.CompareAndSwapUintptr()

该操纵简称CAS(Compare and Swap)。这类操作的前缀为CompareAndSwap。

该操作在进行交换前首先确保被操作数的值未被更改,即仍然保存着 参数old所记录的值,满足此前提条件下才进行交换操作。CAS的做法蕾丝操作数据库时常见的乐观锁机制。

需要注意到是,当大量的goroutine对变量进行读写操作时,可能导致CAS操作无法成功,这时可以利用for循环多次尝试。

atomic.Value 保证任意值的读写安全

atomic 包里提供了一套store 开头的方法,用来保证各种类型变量的并发写安全,避免其他操作读到了修改变量过程中的藏数据。

1
2
3
4
atomic.StoreInt32()
atomic.StoreInt64()
atomic.StorePointer()
...

如果想并发安全的设置一个结构体的多个字段,可以通过atomic.Value实现。

atomic.Value对外暴露了两个方法:

  • v.Store(c) - 写操作,将原始的变量c存放到一个atomic.Value类型的v里。
  • c := v.Load() - 读操作,从线程安全的v中读取上一步中存放的内容。

由于 Load 返回的是一个interface{} 类型,所以在使用前需要先转换成具体类型的值,再使用。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var rect atomic.Value

func update(width, length int) {
rectLocal := new(Rectangle)
rectLocal.width = width
rectLocal.length = length
rect.Store(rectLocal)
}

func main() {
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
i:=i
go func() {
defer wg.Done()
update(i, i+5)
}()
}
wg.Wait()
_r := rect.Load().(*Rectangle)
fmt.Printf("Rectangle width= %v, length = %v\n", _r.width, _r.length)
}

互斥锁

互斥锁实现原子操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main()  {
var count int
var wg sync.WaitGroup
var mux sync.Mutex
for i:=0; i<10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mux.Lock()
count+=1
mux.Unlock()
}()
}
wg.Wait()
fmt.Println("count= ",count)
}

互斥锁于原子锁的区别

  • 使用目的: 互斥锁是用来保护一段逻辑,原子操作用于对一个变量的更新和保护
  • 底层实现:Mutex 由操作系统的调度器实现,而atomic包中的原子操作则有底层硬件指令提供支持,这些指令在执行过程中是不允许中断的,因此原子操作可以 lockk-free的情况下保证并发安全,并且他的性能也能随cpu个数的增多而现行扩展。

对于一个变量更新的保护,原子操作通常会更具有效率,并且能利用计算机多核的优势。

参考

Search by:GoogleBingBaidu