atomic

原子包是比互斥锁更底层的包,如果在简单的场景下,使用 sync.Mutex 可能会比较复杂,并且耗费资源,那么使用更加底层的 atomic 就更加划算了

所谓原子操作,就是当某 goroutine 去执行原子操作时,其它 goroutine 只能看着,这个操作要么成功,要么失败,不会有第三个状态

原子包操作对象的时候,都是操作的地址,所以谨记不要使用值操作而是要指针操作

介绍一下 atomic 的内容

  • Add:例如 func AddInt32(addr *int32,delta int32)(new int32) 给第一个参数地址指向的数据值增加一个 delta 并返回新的数据
  • CompareAndSwap:例如 func CompareAndSwapInt32(addr *int32,old,new int32)(swapped bool) 比较 addr 指向的数据是否等于 old,如果不等于返回 false,如果等于就将此地址的值切换为 new 值,并且返回 true
  • Load:例如 func LoadInt32(addr *int32)(val int32) 读取 addr 指向的值并返回
  • Store:例如 func StoreInt32(addr *int32,val int32) 将 val 值写入到 addr 指向的内存空间中
  • Swap:例如 func SwapInt32(addr *int32,new int32)(old int32) 将 addr 指向的值切换为 new 值,并返回旧值
  • Value:type Value func(*Value) Load func(*Value) Store 原子的存取数据

目前 atomic 还没有部署泛型,所以里面到处充斥者 LoadInt32 LoadInt64 这种类型的函数,以后等泛型部署到原子包后就不会这么繁琐了

基于原子库的第三方扩展

  • uber-go/atomic 定义扩展了几种常见类型的原子操作,例如 bool error string 等

    var atom atomic.Uint32
    atom.Store(42)
    atom.Sub(2)
    atom.CompareAndSwap(40, 11)
    

    看起来的确比官方提供的原子包更加简洁一些

issues

对一个地址的赋值是原子操作吗?

如果对于单核处理器的机器来说,地址的赋值是原子操作

在现在的系统中,write 的地址基本上都是对齐的

对齐地址的写,不会导致其他人看到只写了一半的数据,因为它通过一个指令就可以实现对地址的操作,如果地址不是对齐的话,那么,处理器就需要分成两个指令去处理,如果执行了一个指令,其它人就会看到更新了一半的错误的数据,这被称做撕裂写

所以,你可以认为赋值操作是一个原子操作

但是,对于现代的多处理多核的系统来说,由于 cache、指令重排,可见性等问题,我们 对原子操作的意义有了更多的追求。

在多核系统中,一个核对地址的值的更改,在更新到主内存中之前,是在多级缓存中存放的。这时,多个核看到的数据可能是不一样的

多处理器多核心系统为了处理这类问题,使用了一种叫做内存屏障 (memory fence 或 memory barrier) 的方式。一个写内存屏障会告诉处理器,必须要等到它管道中的未完成 的操作 (特别是写操作) 都被刷新到内存中,再进行操作。

atomic 包提供的方法会提供了一些的功能,不仅仅可以保证赋值的数据完整性,还能保证数据的可见性,一旦一个核更新了该地址的值,其它处理器总是能读取到它的最新值。

atomic 包主要利用了以下几点技术:

  • 编译器插入内存屏障 (Memory Barrier) 指令 编译器会在 atomic 操作前后插入内存屏障指令,来限制 CPU 的乱序执行,保证在该操作前的读写操作都完成,之后的读写都待其完成后再执行。

  • 硬件支持的原子 CPU 指令 如 X86 的 LOCK 指令可以将某些指令变为原子指令。atomic 会利用 CPU 提供的这些原子指令实现加锁。

  • 缓存一致性硬件协议 如 Intel 的 MESI 协议可以在多核间保证缓存的一致性。atomic 利用缓存一致性,使得多个核心缓存中的数据版本是一致的。

  • 核心间互斥 atomic 中的原子操作会在多核间加锁,保证同时只有一个核心可以操作共享变量。

需要注意的是,因为需要处理器之间保证数据的一致性,atomic 的操作是会降低性能的。

综上所述,对于单核机器来说,普通的地址赋值就是原子操作,但是对于多核机器来说,不属于原子操作,原子包去进行的赋值一定是原子操作

参考资料

  • go.dev
  • https://time.geekbang.org/column/intro/100061801
  • 《go 进阶训练营》
  • 《go 语言精进之路》