【Go并发编程】 atomic

105 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 23 天,点击查看活动详情

atomic

在 Go 语言中,atomic 包提供了一系列原子操作函数,用于对内存的读取、修改操作进行原子性的保障,从而实现并发安全。

原子操作:原子操作指的是在执行过程中不能被其他操作中断的操作。在执行的时候,其它线程不会看到执行一半的操作结果。在其它线程看来,原子操作要么执行完了,要么还没有执行,就像一个最小的粒子原子一样,不可分割。原子操作基于底层硬件提供的原子指令实现,这些指令保证了在执行原子操作期间,不会发生中断或者并发访问,因此能够保证操作的原子性。

atomic 提供的方法

Addxxx

AddInt32、AddInt64、AddUint32、AddUint64、AddUintptr

可以实现原子加减操作

a := int64(5)
atomic.AddInt64(&a, 5)

Storexxx、Loadxxx

StoreInt32、StoreInt64、StoreUint32、StoreUint64、StoreUintptr、StorePointer LoadInt32、LoadInt64、LoadUint32、LoadUint64、LoadUintptr、LoadPointer 原子写入与读取。当地址没有对齐,处理器就需要分成两个指令去处理,如果执行了一个指令,其它人就会看到更新了一半的错误的数据,这被称做撕裂写(torn write)。

多处理器多核心系统为了处理这类问题,使用了一种叫做内存屏障(memory fence 或memory barrier)的方式。一个写内存屏障会告诉处理器,必须要等到它管道中的未完成的操作(特别是写操作)都被刷新到内存中,再进行操作。此操作还会让相关的处理器的CPU 缓存失效,以便让它们从主存中拉取最新的值。

atomic 包提供的方法会提供内存屏障的功能,所以,atomic 不仅仅可以保证赋值的数据完整性,还能保证数据的可见性,一旦一个核更新了该地址的值,其它处理器总是能读取到它的最新值。但是,需要注意的是,因为需要处理器之间保证数据的一致性,atomic 的操作也是会降低性能的。

CAS (CompareAndSwap)

CompareAndSwapInt32、CompareAndSwapInt64、CompareAndSwapUint32、CompareAndSwapUint64、CompareAndSwapUintptr、CompareAndSwapPointer

func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)

功能就是原子操作:

  • 当addr==old时,addr=new,返回true
  • 当addr!=old时,返回false

可以把这个当作乐观锁,先load,然后修改,最后保存的时候用CAS,如果有人已经改了,就重试:

a := int64(5)
for {
   old := atomic.LoadInt64(&a)
   new := old + 5
   if atomic.CompareAndSwapInt64(&a, old, new) {
      break
   }
}
fmt.Println(a)

Swap

SwapInt32、SwapInt64、SwapUint32、SwapUint64、SwapUintptr、SwapPointer 直接原子交换。

Value

atomic 还提供了一个特殊的类型:Value。它可以原子地存取对象类型, CAS 和 Swap。我们就可以利用Value存储结构体,并对其进行原子操作了。

type Config struct {
   NodeName string
   Addr     string
   Count    int32
}

func loadNewConfig() Config {
   return Config{NodeName: "北京", Addr: "10.77.95.27", Count: rand.Int31()}
}
func main() {
   var config atomic.Value
   config.Store(loadNewConfig())
   var cond = sync.NewCond(&sync.Mutex{}) // 设置新的config
   go func() {
      for {
         time.Sleep(time.Duration(5+rand.Int63n(5)) * time.Second)
         config.Store(loadNewConfig())
         cond.Broadcast() // 通知等待着配置已变更
      }
   }()
   go func() {
      for {
         cond.L.Lock()
         cond.Wait()                 // 等待变更信号
         c := config.Load().(Config) // 读取新的配置
         fmt.Printf("new config: %+v\n", c)
         cond.L.Unlock()
      }
   }()
   select {}
}