Golang 标准库 sync/atomic
1. 基本概念
原子操作:指的是一个操作或一系列操作在被CPU调度的时候不可中断。即在并发中,保证多CPU对同一块内存的操作是原子性的。
原子操作的实现方式:
- 总线加锁:CPU和其他硬件的通信通过总线控制,所以可以通过Lock总线的方式实现原子操作,但这样会阻塞其他硬件对CPU的访问,开销太大
- 缓存锁定:频繁使用的内存会被处理器放进高速缓存中,那么原子操作就可以直接在处理器的高速缓存中进行,主要依靠缓存的一致性来确保其原子性
Golang 中的原子操作:sync/atomic
包
能够进行原子操作的类型:int32, int64, uint32, uint64, uintptr, unsafe.Pointer
五种操作函数:增或减、比较并交换、载入、存储、交换
原子操作比锁更为高效。
2. 原子操作 vs 锁
- 加锁比较耗时,需要上下文切换。即使是goroutine也需要上下文切换
- 只针对基本类型,可使用原子操作保证线程安全
- 原子操作在用户态完成,性能比互斥锁要高
- 原子操作步骤简单,不需要加锁-操作-解锁
3. 五种操作
- 增或减 (Add)
- 比较并交换 (CAS, Compare & Swap)
- 载入 (Load)
- 存储 (Store)
- 交换 (Swap)
3.1 增或减
func AddInt64(addr *int64, delta int64) (new int64)
func main() {
var n int64
for i := 0; i <= 100; i++ {
go func(i int) {
//n += int64(i) // 无法保证原子性
atomic.AddInt64(&n, int64(i))
time.Sleep(time.Millisecond)
}(i)
}
time.Sleep(time.Second)
fmt.Println(atomic.LoadInt64(&n))
}
3.2 载入
当读取的时候,任何其他CPU操作都无法对该变量进行读写
func LoadInt64(addr *int64) (val int64)
3.3 比较并交换
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
CAS操作,在进行交换前,首先确保变量的值未被更改,即仍然保持参数 old
所记录的值,满足此前提下才进行交换操作。CAS的做法类似操作数据库时常见的乐观锁机制。
注意:当有大量 goroutine 对变量进行读写操作时,可能导致CAS操作无法成功,此时要利用for循环多次尝试。
var N int64
func atomicAddOp(i int64) {
// 可能不成功的操作
//tmp := atomic.LoadInt64(&N)
//swapped := atomic.CompareAndSwapInt64(&N, tmp, tmp+i)
//fmt.Printf("%d try to CAS: %v\n", tmp, swapped)
for {
tmp := atomic.LoadInt64(&N)
swapped := atomic.CompareAndSwapInt64(&N, tmp, tmp+i)
fmt.Printf("%d try to CAS: %v\n", tmp, swapped)
if swapped {
break
}
}
time.Sleep(time.Millisecond)
}
func main() {
for i := 0; i <= 100; i++ {
go atomicAddOp(int64(i))
}
time.Sleep(time.Second)
fmt.Println(atomic.LoadInt64(&N))
}
3.4 存储
func StoreInt64(addr *int64, val int64)
此操作可确保写变量的原子性,避免其他操作读到修改变量过程中的脏数据。
3.5 交换
func SwapInt64(addr *int64, new int64) (old int64)
4. 原子值
type Value struct {
v interface{}
}
func (v *Value) Load() (x interface{})
func (v *Value) Store(x interface{})
type AtomicArray interface {
Set(idx uint32, elem int) error
Get(idx uint32) (int, error)
Len() uint32
}
type Array struct {
value atomic.Value
length uint32
}
func (a *Array) checkIndex(idx uint32) (err error) {
if a.length <= idx {
err = errors.New("array out of range")
}
return
}
func NewArray(arr []int) Array {
val := atomic.Value{}
val.Store(arr)
return Array{val, uint32(len(arr))}
}
func (a *Array) Set(idx uint32, elem int) (err error) {
if err = a.checkIndex(idx); err != nil {
return
}
newArr := make([]int, a.length)
copy(newArr, a.value.Load().([]int))
newArr[idx] = elem
a.value.Store(newArr)
return
}
func (a *Array) Len() uint32 {
return a.length
}
func (a *Array) Get(idx uint32) (int, error) {
if err := a.checkIndex(idx); err != nil {
return 0, err
}
arr := a.value.Load().([]int)
return arr[idx], nil
}
func main() {
a := NewArray([]int{5, 3, 6, 2, 8})
fmt.Println(a.length)
elem, err := a.Get(3)
if err != nil {
panic(err)
}
fmt.Println(elem)
err = a.Set(3, 10)
if err != nil {
panic(err)
}
elem, err = a.Get(3)
if err != nil {
panic(err)
}
fmt.Println(elem)
}
5. 实例
5.1 单例
type singleton struct{}
var (
instance *singleton
initialized uint32
mu sync.Mutex
)
func Instance() *singleton {
if atomic.LoadUint32(&initialized) == 1 {
return instance
}
mu.Lock()
defer mu.Unlock()
if instance == nil {
defer atomic.StoreUint32(&initialized, 1)
instance = &singleton{}
}
return instance
}
基于sync.Once
实现:
type singleton struct{}
var (
instance *singleton
once sync.Once
)
func Instance() *singleton {
once.Do(func() {
instance = &singleton{}
})
return instance
}
```sync.Once`源码:
type Once struct {
m sync.Mutex
done uint32
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 1 {
return
}
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
5.2 处理配置
func loadConfig() map[string]string {
return make(map[string]string)
}
func requests() chan int {
return make(chan int)
}
func main() {
var config atomic.Value
// 初始化配置信息
config.Store(loadConfig())
// 启动一个协程,刷新配置信息
go func() {
for {
time.Sleep(3 * time.Second)
config.Store(loadConfig())
}
}()
// 工作线程读取配置信息
for i := 0; i < 10; i++ {
go func() {
for r := range requests() {
c := config.Load()
// ...
}
}()
}
}