int64MOV
32机器上对int64进行赋值

如果一个线程刚写完低32位,还没来得及写高32位时,另一个线程读取了这个变量,那它得到的就是一个毫无逻辑的中间变量,这很有可能使我们的程序出现Bug。

这还只是一个基础类型,如果我们对一个结构体进行赋值,那它出现并发问题的概率就更高了。很可能写线程刚写完一小半的字段,读线程就来读取这个变量,那么就只能读到仅修改了一部分的值。这显然破坏了变量的完整性,读出来的值也是完全错误的。

Goatomic.Valueunsafe.Pointer
atomic.Value

atomic.Value的使用方式

atomic.Value
v.Store(c)catomic.Valuevc := v.Load()v
atomic.Value
type Rectangle struct {
 length int
 width  int
}

var rect atomic.Value

func update(width, length int) {
 rectLocal := new(Rectangle)
 rectLocal.width = width
 rectLocal.length = length
 rect.Store(rectLocal)
}

func main() {
 wg := sync.WaitGroup{}
 wg.Add(10)
 // 10 个协程并发更新
 for i := 0; i < 10; i++ {
  go func() {
   defer wg.Done()
   update(i, i+5)
  }()
 }
 wg.Wait()
 _r := rect.Load().(*Rectangle)
 fmt.Printf("rect.width=%d\nrect.length=%d\n", _r.width, _r.length)
}
atomic.ValueRectange
atomic.Value

atomic.Value的内部实现

atomic.Valueinterface{}
type Value struct {
  v interface{}
}
ValueatomicifaceWordsinterface{}interface{}
// ifaceWords is interface{} internal representation.
type ifaceWords struct {
  typ  unsafe.Pointer
  data unsafe.Pointer
}

写入线程安全的保证

unsafe.Pointer
unsafe.Pointer

出于安全考虑,Go 语言并不支持直接操作内存,但它的标准库中又提供一种不安全(不保证向后兼容性) 的指针类型unsafe.Pointer,让程序可以灵活的操作内存。

unsafe.Pointerunsafe.Pointer
[]bytestringreflect.SliceHeaderreflect.StringHeader
type SliceHeader struct {
 Data uintptr
 Len  int
 Cap  int
}

type StringHeader struct {
 Data uintptr
 Len  int
}
unsafe.Pointer[]bytestring
bytes := []byte{104, 101, 108, 108, 111}

p := unsafe.Pointer(&bytes) //将 *[]byte 指针强制转换成unsafe.Pointer
str := *(*string)(p) //将 unsafe.Pointer再转换成string类型的指针,再将这个指针的值当做string类型取出来
fmt.Println(str) //输出 "hello"
unsafe.Pointer
func (v *Value) Store(x interface{}) {
  if x == nil {
    panic("sync/atomic: store of nil value into Value")
  }
  vp := (*ifaceWords)(unsafe.Pointer(v))  // Old value
  xp := (*ifaceWords)(unsafe.Pointer(&x)) // New value
  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(^uintptr(0))) {
        runtime_procUnpin()
        continue
      }
      // Complete first store.
      StorePointer(&vp.data, xp.data)
      StorePointer(&vp.typ, xp.typ)
      runtime_procUnpin()
      return
    }
    if uintptr(typ) == ^uintptr(0) {
      // 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 != xp.typ {
      panic("sync/atomic: store of inconsistently typed value into Value")
    }
    StorePointer(&vp.data, xp.data)
    return
  }
}

大概的逻辑:

unsafe.PointerifaceWordsinterface{}CompareAndSwapLoadPointerValue
atomic.ValuetyptypValue
runtime_procPin()^uintptr(0)CAStyp^uintptr(0)vdatatyptyp
typ^uintptr(0)
data

这个逻辑的主要思想就是,为了完成多个字段的原子性写入,我们可以抓住其中的一个字段,以它的状态来标志整个原子写入的状态。

读取(Load)操作

先上代码:

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
}

读取相对就简单很多了,它有两个分支:

typ^uintptr(0)typdatainterface{}

总结

atomic.Value
atomic.Value
Mutex