在 Go 语言标准库中,sync/atomic
包将底层硬件提供的原子操作封装成了 Go 的函数。但这些操作只支持几种基本数据类型,Go 语言在 1.4 版本的时候向sync/atomic
包中添加了一个新的类型Value
。此类型的值相当于一个容器,可以被用来“原子地”存储(Store)和加载(Load)任意类型的值。
原子操作
原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分。即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
原子性不可能由软件单独保证–必须需要硬件的支持,因此是和架构相关的。在x86 平台上,CPU提供了在指令执行期间对总线加锁的手段。CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀”LOCK”,经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。
mutex
由操作系统实现,而atomic
包中的原子操作则由底层硬件直接提供支持。因此原子操作可以在lock-free
的情况下保证并发安全,并且它的性能也能做到随 CPU 个数的增多而线性扩展。
atomic
提供的函数功能需要非常小心才能正确使用,除了在一些特殊的low-level程序中,同步最好使用通道或sync包的设施来完成。
atomic
1.4 版本前,sync/atomic
包支持几种基本数据类型的原子操作,之后新增的atomic.Value
类型,供一致类型值的原子加载和存储,支持任意类型的数据,内部的字段是一个interface{}
类型。
|
|
实现解读
参见:Go 语言标准库中 atomic.Value 的前世今生
unsafe.Pointer
unsafe.Pointer 可以绕过 Go 语言类型系统的检查,完成任何类型与内建的 uintptr 类型之间的转化。unsafe.Pointer 可以实现四种其他类型不能的操作:
- 任何类型的指针都可以转化为一个 unsafe.Pointer
- 一个 unsafe.Pointer 可以转化成任何类型的指针
- 一个 uintptr 可以转化成一个 unsafe.Pointer
- 一个 unsafe.Pointer 可以转化成一个 uintptr