一. sync.atomic 基础
- atomic 包中提供许多基本数据类型的原子操作,主要可以分为下面几类:
- 原子交换
- CAS
- 原子加法
- 原子取值
- 原子赋值
- Value
- 原子操作相关方法,将 new 存储到地址 addr 并返回该地址上原来的值
func SwapInt32(addr *int32, new int32) (old int32)
func SwapInt64(addr *int64, new int64) (old int64)
func SwapUint32(addr *uint32, new uint32) (old uint32)
func SwapUint64(addr *uint64, new uint64) (old uint64)
func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)
func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)
- CAS操作相关方法,拿 addr 上的值和 old 比较,如果相等,就把 new 存储到 addr
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)
func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)
func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
- CAS实现轻量级锁示例
func casADD() {
defer w.Done()
for i := 0; i < 10000; i++ {
for old := a; !atomic.CompareAndSwapInt64(&a, old, old + 1); {
old = a
}
}
}
- 原子加法,是给原来 addr 地址上的值加上 delta, 并返回最新的值,注意如果使用 AddUint64 执行 x - c 需要执行 AddUint64(&x, ^uint64(c-1)), 所以原子的 x – 可以写为 AddUint64(&x, ^uint64(0)), uint32 和 AddUint32() 同理
func AddInt32(addr *int32, delta int32) (new int32)
func AddUint32(addr *uint32, delta uint32) (new uint32)
func AddInt64(addr *int64, delta int64) (new int64)
func AddUint64(addr *uint64, delta uint64) (new uint64)
func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)
- 原子取值,从地址 addr 取值并返回
func LoadInt32(addr *int32) (val int32)
func LoadInt64(addr *int64) (val int64)
func LoadUint32(addr *uint32) (val uint32)
func LoadUint64(addr *uint64) (val uint64)
func LoadUintptr(addr *uintptr) (val uintptr)
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
- 原子赋值,将 val 存储到地址 addr
func StoreInt32(addr *int32, val int32)
func StoreInt64(addr *int64, val int64)
func StoreUint32(addr *uint32, val uint32)
func StoreUint64(addr *uint64, val uint64)
func StoreUintptr(addr *uintptr, val uintptr)
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
sync.atomic.Value
- 上面提供大量的针对数值和指针类型的原子操作相关方法,为了扩大原子操作的范围,在 Go 1.4 的加入了sync.atomic.Value
type Value struct {
v interface{}
}
- value使用示例: value 使用起来非常简单可以把它当作一个容器,可以将值存入该容器中,然后通过容器取值,通过容器保证了原子性的,提供了Load() 和 Store() 两个方法
- Load(): 安全地从内存中读取值,
- Store(): 将值安全地存入内存
type S struct {
a int
}
func main() {
var v atomic.Value
s := S{1}
v.Store(s)
p := v.Load()
fmt.Println(p.(S).a)
}
二. sync.atomic 源码分析
1. ifaceWords
- 了解sync.atomic底层,首先要了解Go底层提供的一个私有的结构体 ifaceWords,内部含 typ 和 data 两个指针类型属性,前者表示值的真实类型,后者表示值的“值”,通过unsafe.Pointer 转换成 ifaceWords, 可以得到 interface{} 真实的类型和值
type ifaceWords struct {
typ unsafe.Pointer
data unsafe.Pointer
}
- ifaceWords用于表示接口类型变量的底层数据结构,在 sync/atomic 包中通过ifaceWords,实现对接口类型变量进行原子性操作的功能,相对于使用 Eface 和 Iface 类型来存储接口类型变量的值,使用 ifaceWords 可以带来更高的效率和更小的内存开销。因为 ifaceWords 直接存储了具体类型信息和实际数据指针,而不需要像 Eface 和 Iface 一样再额外封装一层。因此,在某些场景下,使用 ifaceWords 非常适合进行内存优化和性能优化
2. Store()添加
- 在通过Store()存储数据时,底层的执行顺序:
- 先判断存储的数据是否为nil
- 将原数据与现在要存储的数据强转为ifaceWords类型
- 通过LoadPointer()获取到原值的真实数据类型,如果为nil说明第一次存储,先调用runtime_procPin()禁止抢占,并且防止GC执行,然后调用CompareAndSwapPointer()比较并交换进行原子更新,如果存储失败则continue自旋重试
- 如果不是第一次存储数据,进行数据类型校验,校验成功后调用StorePointer(),把新值 x 的类型和值存储在 v 的地址上
func (v *Value) Store(x interface{}) {
//1.判断是否为nil
if x == nil {
panic("sync/atomic: store of nil value into Value")
}
//2.将原值与现在添加的值强转为ifaceWords类型
//将Value类型的对象v转成ifaceWords类型的对象,因为v的底层结构与ifaceWords是相同的
vp := (*ifaceWords)(unsafe.Pointer(v))
//将入参对象x转成ifaceWords类型的对象,x为interface{}类型,底层结构与ifaceWords是相同的
xp := (*ifaceWords)(unsafe.Pointer(&x))
//3.自旋
for {
//获取原值的真实数据类型
typ := LoadPointer(&vp.typ)
//如果原值为nil,说明是第一次存储值
if typ == nil {
//禁止抢占,防止 GC 看到 unsafe.Pointer(^uintptr(0)) 这个奇怪的类型
runtime_procPin()
//通过原子性操作,存储数据,如果存储失败continue重试
if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
//释放禁止抢占
runtime_procUnpin()
continue // 比较不通过,说明有别人在执行赋值,自旋等待
}
//分别将入参中的type和data存储到v中,注意这里是先存储data然后存储typ,
//因为程序以typ是否设置完成,来判断整个存储操作全部完成
StorePointer(&vp.data, xp.data) // 设置新置
StorePointer(&vp.typ, xp.typ) // 设置类型
runtime_procUnpin()
return
}
//该判断返回true,说明赋值没结束,自旋等待
if uintptr(typ) == ^uintptr(0) {
continue
}
//4.当执行到此处说明不是第一次存储,判断此次添加的数据,类型与原始值是否相同
if typ != xp.typ {
panic("sync/atomic: store of inconsistently typed value into Value")
}
//把 x 写入 v
// 只有第一次需要设置 tpy, 后面只需要设置 data
StorePointer(&vp.data, xp.data)
return
}
}
3. Load()获取
- Load相对简单, 通过 ifaceWords 拿到 v 的真实类型,如果 v 中没有存值或正在写入,他会直接返回 nil,否则就把 v.data 和 v.typ 重新组装成 interface{} 返回
func (v *Value) Load() (x interface{}) {
vp := (*ifaceWords)(unsafe.Pointer(v))
typ := LoadPointer(&vp.typ)
if typ == nil || uintptr(typ) == ^uintptr(0) {
// First store not yet completed.
return nil
}
data := LoadPointer(&vp.data)
xp := (*ifaceWords)(unsafe.Pointer(&x))
xp.typ = typ
xp.data = data
return
}