atomic

在之前的源码分析中,我们有多次看到atomic的出现,今天不妨我们看一下atomic的源码。


我们看一下atomic的源码文件:

img

这里说明一下,以.s为后缀的是汇编语言源代码文件,你可以并不懂汇编,没有关系。

主要看下asm.s,看一看到里面有调用runtime ∕ internal ∕ atomic,我们前去看一下这个文件夹,其中有个文件atomic_wasm.go。

atomic提供的是原子操作,atomic包中支持六种类型

  • int32
  • uint32
  • int64
  • uint64
  • uintptr
  • unsafe.Pointer

对于每一个类型,支持5种操作,我们以int32分别说明下这些操作:

SwapX

// 原子性的将新值保存到*addr并返回旧值。
func SwapInt32(addr *int32, new int32) (old int32)

// 源码
func Xchg(ptr *uint32, new uint32) uint32 {
	old := *ptr
	*ptr = new
	return old
}

CompareAndSwapX

// 原子性的比较*addr和old,如果相同则将new赋值给*addr并返回真。
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)

// 源码
func Cas(ptr *uint32, old, new uint32) bool {
	if *ptr == old {
		*ptr = new
		return true
	}
	return false
}

AddX

// 原子性的将val的值添加到*addr并返回新值
func AddInt64(addr *int64, delta int64) (new int64)

// 源码
func Xadd(ptr *uint32, delta int32) uint32 {
	new := *ptr + uint32(delta)
	*ptr = new
	return new
}

LoadX

// 原子性的获取*addr的值
func LoadInt32(addr *int32) (val int32)

// 源码
func Load(ptr *uint32) uint32 {
	return *ptr
}

StoreX

// 原子性的将val的值保存到*addr
func StoreInt32(addr *int32, val int32)

// 源码
func Store(ptr *uint32, val uint32) {
	*ptr = val
}

源码其实比较简单了,我就不过多说明了。

atomic.Value

另外,atomic对支持的类型做了扩展,atomic.Value被设计用来存储任意类型的数据。

type Value struct {
	v interface{}
}

为了方便,定义了一个ifaceWords类型,它的作用是将interface{}类型分解,得到其中的两个字段,作为interface的内部表示格式,typ代表原始类型,data代表真正的值。

type ifaceWords struct {
	typ  unsafe.Pointer
	data unsafe.Pointer
}

提供了Store和Load两个方法。

Store

func (v *Value) Store(x interface{}) {
        // x为nil,直接panic
	if x == nil {
		panic("sync/atomic: store of nil value into Value")
	}
	// 将现有的值和要写入的值转换为ifaceWords类型,这样下一步就能获取到它们的原始类型和真正的值
	vp := (*ifaceWords)(unsafe.Pointer(v))
	xp := (*ifaceWords)(unsafe.Pointer(&x))
	for {
		// 获取现有的值的type
		typ := LoadPointer(&vp.typ)
		// 如果typ为nil说明这是第一次调用Store
		if typ == nil {
			// 如果是第一次调用,就占住当前的processor,不允许其他goroutine再抢,runtime_procPin方法会先获取当前goroutine
			runtime_procPin()
			// 使用CAS操作,先尝试将typ设置为^uintptr(0)这个中间状态
			// 如果失败,则证明已经有别的goroutine抢先完成了赋值操作
			// 那它就解除抢占锁,继续循环等待
			if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
				runtime_procUnpin()
				continue
			}
			// 如果设置成功,就原子性的更新对应的指针,最后解除抢占锁
			StorePointer(&vp.data, xp.data)
			StorePointer(&vp.typ, xp.typ)
			runtime_procUnpin()
			return
		}
		// 如果typ为^uintptr(0)说明第一次写入还没有完成,继续循环等待
		if uintptr(typ) == ^uintptr(0) {
			continue
		}
		// 如果要写入的类型和现有的类型不一致,则panic
		if typ != xp.typ {
			panic("sync/atomic: store of inconsistently typed value into Value")
		}
		// 更新data,跳出循环
		StorePointer(&vp.data, xp.data)
		return
	}
}

Load

func (v *Value) Load() (x interface{}) {
        // 将*Value指针类型转换为*ifaceWords指针类型
	vp := (*ifaceWords)(unsafe.Pointer(v))
	// 原子性的获取到v的类型typ的指针
	typ := LoadPointer(&vp.typ)
	// 如果没有写入或者正在写入,先返回,^uintptr(0)代表过渡状态,这和Store是对应的
	if typ == nil || uintptr(typ) == ^uintptr(0) {
		return nil
	}
	// 原子性的获取到v的真正的值data的指针,然后返回
	data := LoadPointer(&vp.data)
	xp := (*ifaceWords)(unsafe.Pointer(&x))
	xp.typ = typ
	xp.data = data
	return
}

PS:解读的源码,如无特别说明,版本为1.15.6


参考:


atomic
https://blog.puresai.com/2021/02/13/atomic/
作者
puresai
许可协议