这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战
atomic
atomic是Go内置的原子操作包,GO的官方声明中提示,atomic提供了实现同步机制的底层原子内存原语,使用时需要非常小心,除了特殊的底层应用程序外,最后使用sync包或channel来进行同步。所以说atomic是一个很底层的包,仅在一些底层应用程序或者一些追求性能的地方使用,不要随意使用,容易造成问题并且不好排查。
底层实现
atomic底层是通过汇编语言实现的,是由底层硬件来提供支持的,在不同的CPU的架构中,实现方式也是不一样的。 在Intel的CPU架构机器上,是通过总线锁的方式实现。即当一个CPU需要操作一块内存的时候,先向总线发送一个LOCK信号,所有CPU收到这个信号后,就不对这块内存进行操作了。等待加锁的CPU操作完成后,在发送UNLOCK信号,其它CPU即可正常访问了。这个其实就是CPU级别的锁,只不过粒度非常小,所以性能要比程序级的锁好的多。在AMD的CPU架构机器上,使用的就是MESI一致性协议的方式来实现的。
基本使用
atomic包提供的操作大致可分为三类
对整数类型T的操作(T:int32、int64、uint32、uint64、uintptr)
func AddT(addr *T, delta T) (new T)
func CompareAndSwapT(addr *T, old, new T) (swapped bool)
func LoadT(addr *T) (val T)
func StoreT(addr *T, val T)
func SwapT(addr *T, new T) (old T)
unsafe.Pointer类型提供的操作
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)
unsafe.Pointer是一个很特殊的指针类型,让程序可以灵活的操作内存,并且unsafe.Pointer可以绕过Go的类型系统检查,与任意指针类型互相转换。有一个很经典的转换就是[]byte数组直接转换成string类型。
[]byte和string其实内部的存储结构都是一样的
bytes := []byte{104, 101, 108, 108, 111}
p := unsafe.Pointer(&bytes) //强制转换成unsafe.Pointer,编译器不会报错
str := *(*string)(p) //然后强制转换成string类型的指针,再将这个指针的值当做string类型取出来
fmt.Println(str) //输出 "hello"
atomic.Value类型提供的操作
func (v *Value) Load() (x interface{}) // 原子性返回刚刚存储的值,若没有值返回nil\
func (v *Value) Store(x interface{}) // 原子性存储值x,x可以是nil,但需要每次存的值都必须是同一个具体类型。
原本atomic包只支持几种基本数据类型,适用范围太小,基于适用者需求的增加,GO再1.4版本的时候,添加了atomic.Value这个新类型,这个类型相当于一个容器,可以被用来“原子地"存储(Store)和加载(Load)任意类型的值。(感觉有几分泛型的意思?)
atomic.Value需要存储任意类型的数据,所以没错,它内部其实就是一个空接口
使用很简单,只需将需要作并发保护的变量读取和赋值操作用Load()和Store()代替就行了。
注意Store()方法传入的值不能为 nil,并且类型必须是相同的,不然会导致 panic