Skip to content

Latest commit

 

History

History

atomic

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

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 的操作是会降低性能的。

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

参考资料