1. Go中的原子操作
原子性:一个或多个操作在CPU的执行过程中不被中断的特性,称为原子性。这些操作对外表现成一个不可分割的整体,他们要么都执行,要么都不执行,外界不会看到他们只执行到一半的状态。
原子操作:进行过程中不能被中断的操作,原子操作由底层硬件支持,而锁则是由操作系统提供的API实现,若实现相同的功能,前者通常会更有效率
最小案例:
package main
import (
"sync"
"fmt"
)
var count int
func add(wg *sync.WaitGroup) {
defer wg.Done()
count++
}
func main() {
wg := sync.WaitGroup{}
wg.Add(1000)
for i := 0; i < 1000; i++ {
go add(&wg)
}
wg.Wait()
fmt.Println(count)
}
count不会等于1000,因为count++这一步实际是三个操作:
- 从内存读取count
- CPU更新count = count + 1
- 写入count到内存
因此就会出现多个goroutine读取到相同的数值,然后更新同样的数值到内存,导致最终结果比预期少
2. Go中sync/atomic包
Go语言提供的原子操作都是非入侵式的,由标准库中sync/aotomic中的众多函数代表
atomic包中支持六种类型
- int32
- uint32
- int64
- uint64
- uintptr
- unsafe.Pointer
对于每一种类型,提供了五类原子操作:
- LoadXXX(addr): 原子性的获取*addr的值,等价于:
return *addr
- StoreXXX(addr, val): 原子性的将val的值保存到*addr,等价于