GO源码阅读——sync(1)

372 阅读6分钟

sync

atomic

asm.s

#include "textflag.h"
​
TEXT ·SwapInt32(SB),NOSPLIT,$0
    JMP runtime∕internal∕atomic·Xchg(SB)

这段代码是的作用是用于交换两个32位整数的值。

具体来说,这段代码定义了一个名为SwapInt32的函数,它是一个不需要栈分裂(NOSPLIT)的函数,并且它没有本地变量($0)。该函数通过跳转到runtime∕internal∕atomic·Xchg(SB)函数来实现整数交换操作。

value.go

// LoadPointer atomically loads *addr.
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
​
// StorePointer atomically stores val into *addr.
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
​
// CompareAndSwapPointer executes the compare-and-swap operation for a unsafe.Pointer value.
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
​
// SwapPointer atomically stores new into *addr and returns the previous *addr value.
func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)

Value提供一致类型化值的原子加载和存储

type Value struct {
    v any
}

ifaceWords 是 interface{} 的底层结构体。由类型指针和数据指针,两个指针构成,所以interface{}的修改并不是原子的。

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

Load 返回由最新Store()设置的值。如果还没有为此值调用 Store(),则返回 nil。

func (v *Value) Load() (val any) {
    vp := (*ifaceWords)(unsafe.Pointer(v))
    typ := LoadPointer(&vp.typ)
    if typ == nil || typ == unsafe.Pointer(&firstStoreInProgress) {
        // First store not yet completed.
        return nil
    }
    data := LoadPointer(&vp.data)
    vlp := (*ifaceWords)(unsafe.Pointer(&val))
    vlp.typ = typ
    vlp.data = data
    return
}

首先,将Value类型的指针v转换为ifaceWords类型的指针vpifaceWords用于存储接口类型和实际的数据值。unsafe.Pointer函数用于将指针转换为通用的指针类型。

接下来,通过LoadPointer函数加载vp.typ字段的值,并将其赋给typ变量。如果typ的值为nil或者等于unsafe.Pointer(&firstStoreInProgress),说明第一个存储操作还未完成,直接返回nil表示读取失败。

如果typ的值不为nil且不等于unsafe.Pointer(&firstStoreInProgress),则说明第一个存储操作已经完成。继续通过LoadPointer函数加载vp.data字段的值,并将其赋给data变量。

然后,将val转换为ifaceWords类型的指针vlp,并将typdata赋值给vlp.typvlp.data字段,最后将val返回。这样就完成了对Value类型的值的读取操作。

Store实现了对Value类型的存储操作,保证了原子性和类型正确性。

func (v *Value) Store(val any) {
    if val == nil {
        panic("sync/atomic: store of nil value into Value")
    }
    vp := (*ifaceWords)(unsafe.Pointer(v))
    vlp := (*ifaceWords)(unsafe.Pointer(&val))
    for {
        typ := LoadPointer(&vp.typ)
        if typ == nil {
            // Attempt to start first store.
            // Disable preemption so that other goroutines can use
            // active spin wait to wait for completion.
            runtime_procPin()
            if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(&firstStoreInProgress)) {
                runtime_procUnpin()
                continue
            }
            // Complete first store.
            StorePointer(&vp.data, vlp.data)
            StorePointer(&vp.typ, vlp.typ)
            runtime_procUnpin()
            return
        }
        if typ == unsafe.Pointer(&firstStoreInProgress) {
            // First store in progress. Wait.
            // Since we disable preemption around the first store,
            // we can wait with active spinning.
            continue
        }
        // First store completed. Check type and overwrite data.
        if typ != vlp.typ {
            panic("sync/atomic: store of inconsistently typed value into Value")
        }
        StorePointer(&vp.data, vlp.data)
        return
    }
}

首先,判断传入的值val是否为nil,如果是则抛出异常,不能将nil值存储到Value类型中。

然后,将Value类型的指针vval转换为ifaceWords类型的指针vpvlp,并通过LoadPointer函数加载vp.typ字段的值,赋给typ变量。

如果typ的值为nil,说明当前还没有进行过存储操作,因此尝试开始第一个存储操作。通过调用runtime_procPin函数禁用抢占,让其他goroutine可以通过主动自旋等待存储操作的完成。然后通过CompareAndSwapPointer函数将vp.typ字段的值从nil修改为unsafe.Pointer(&firstStoreInProgress),如果修改失败则继续自旋等待。如果修改成功,则表示第一个存储操作已经开始,继续通过StorePointer函数将vp.datavp.typ字段的值设置为vlp.datavlp.typ的值,完成第一个存储操作,并通过runtime_procUnpin函数解除抢占禁用。

如果typ的值为unsafe.Pointer(&firstStoreInProgress),说明第一个存储操作还在进行中,继续自旋等待。

如果typ的值不为nil且不等于unsafe.Pointer(&firstStoreInProgress),则说明已经完成了第一个存储操作。此时检查typ的值是否与vlp.typ相等,如果不相等则抛出异常。否则,通过StorePointer函数将vp.data的值设置为vlp.data的值,完成存储操作。

Swap 用于原子地进行交换值

func (v *Value) Swap(new any) (old any) {
    if new == nil {
        panic("sync/atomic: swap of nil value into Value")
    }
    vp := (*ifaceWords)(unsafe.Pointer(v))
    np := (*ifaceWords)(unsafe.Pointer(&new))
    for {
        typ := LoadPointer(&vp.typ)
        if typ == nil {
            // Attempt to start first store.
            // Disable preemption so that other goroutines can use
            // active spin wait to wait for completion; and so that
            // GC does not see the fake type accidentally.
            runtime_procPin()
            if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(&firstStoreInProgress)) {
                runtime_procUnpin()
                continue
            }
            // Complete first store.
            StorePointer(&vp.data, np.data)
            StorePointer(&vp.typ, np.typ)
            runtime_procUnpin()
            return nil
        }
        if typ == unsafe.Pointer(&firstStoreInProgress) {
            // First store in progress. Wait.
            // Since we disable preemption around the first store,
            // we can wait with active spinning.
            continue
        }
        // First store completed. Check type and overwrite data.
        if typ != np.typ {
            panic("sync/atomic: swap of inconsistently typed value into Value")
        }
        op := (*ifaceWords)(unsafe.Pointer(&old))
        op.typ, op.data = np.typ, SwapPointer(&vp.data, np.data)
        return old
    }
}

这段代码实现了 Value 类型的 Swap 方法,用于原子地交换值。该方法首先检查 new 参数是否为 nil,如果是,则抛出 panic 异常。然后通过将 Value 类型转换为 ifaceWords 结构体的指针来访问其内部字段。接下来,循环检查 typ 字段是否为 nil。如果是 nil,则尝试开始第一个存储操作,并将 Value 类型设置为一个特殊的指针 &firstStoreInProgress。在这种情况下,为了避免其他 goroutine 干扰存储操作,该方法会禁用抢占,并采用主动自旋的方式等待存储操作完成。一旦第一个存储操作完成,Swap 方法会将 old 参数的值设置为原来 Value 类型的值,然后再用新值替换它。如果 typ 字段不为 nil,则检查它的值是否与新值的类型相同,如果不同,则抛出 panic 异常。如果类型匹配,则使用 SwapPointer 方法原子地交换数据字段的值,并将旧值返回。由于存在并发写入的情况,该方法会在必要时通过自旋等待其他 goroutine 完成其操作,而不会被阻塞。

CompareAndSwap方法用于比较并交换一个 Value 类型的值。

func (v *Value) CompareAndSwap(old, new any) (swapped bool) {
    if new == nil {
        panic("sync/atomic: compare and swap of nil value into Value")
    }
    vp := (*ifaceWords)(unsafe.Pointer(v))
    np := (*ifaceWords)(unsafe.Pointer(&new))
    op := (*ifaceWords)(unsafe.Pointer(&old))
    if op.typ != nil && np.typ != op.typ {
        panic("sync/atomic: compare and swap of inconsistently typed values")
    }
    for {
        typ := LoadPointer(&vp.typ)
        if typ == nil {
            if old != nil {
                return false
            }
            // Attempt to start first store.
            // Disable preemption so that other goroutines can use
            // active spin wait to wait for completion; and so that
            // GC does not see the fake type accidentally.
            runtime_procPin()
            if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(&firstStoreInProgress)) {
                runtime_procUnpin()
                continue
            }
            // Complete first store.
            StorePointer(&vp.data, np.data)
            StorePointer(&vp.typ, np.typ)
            runtime_procUnpin()
            return true
        }
        if typ == unsafe.Pointer(&firstStoreInProgress) {
            // First store in progress. Wait.
            // Since we disable preemption around the first store,
            // we can wait with active spinning.
            continue
        }
        // First store completed. Check type and overwrite data.
        if typ != np.typ {
            panic("sync/atomic: compare and swap of inconsistently typed value into Value")
        }
        // Compare old and current via runtime equality check.
        // This allows value types to be compared, something
        // not offered by the package functions.
        // CompareAndSwapPointer below only ensures vp.data
        // has not changed since LoadPointer.
        data := LoadPointer(&vp.data)
        var i any
        (*ifaceWords)(unsafe.Pointer(&i)).typ = typ
        (*ifaceWords)(unsafe.Pointer(&i)).data = data
        if i != old {
            return false
        }
        return CompareAndSwapPointer(&vp.data, data, np.data)
    }
}

参数 oldnew 分别表示原始值和期望更新的新值。如果 old 的类型与 Value 的类型不匹配,方法将 panic。该方法使用一个 for 循环来执行操作,以确保操作完成。

在循环开始时,将获取 Value 类型的类型信息。如果值为 nil,则表示 Value 目前没有存储任何值。在这种情况下,如果 old 的值不为 nil,则表示不需要执行交换操作,方法将返回 false。否则,方法将尝试执行第一次存储操作。在此期间,将禁用抢占,以便其他 goroutine 可以使用主动自旋等待完成。完成第一次存储后,将启用抢占并返回 true。

如果 Value 的类型信息不为 nil,则将检查其类型信息是否与 new 的类型信息匹配。如果不匹配,将 panic。

接下来,方法将使用 LoadPointer 函数获取 Value 存储的数据指针,并将其与 old 的值进行比较。如果这两个值不相等,则说明当前 Value 的值与 old 的值不匹配,因此方法将返回 false。否则,该方法将使用 CompareAndSwapPointer 函数来尝试交换 Value 的数据指针和 new 的数据指针。如果成功,该方法将返回 true,否则将继续循环。