NOTE

2.11 atomic

What atomicity is, what atomic.Value is, how to use it, and the implementation of Store and Load.

GoCreated Updated 1 min readhistorical

This is a historical learning note and may contain outdated or incomplete understanding.

1. What Is atomic

Atomicity.

  • One or more operations either all execute or none of them execute.
  • The smallest operation that cannot be parallelized means that at most one concurrent execution context can operate on the resource at the same time.
  • In general, atomic operations are guaranteed through mutually exclusive access and are usually protected by special CPU instructions.

2. What Is atomic value

The atomic package wraps atomic operations provided by the underlying hardware as Go functions, but it only supports several basic data types. atomic.Value can atomically Store and Load values of arbitrary types.

3. How to Use It

var (
	count        = 0
	GlobalConfig atomic.Value
	updateSignal chan int = make(chan int)
)

type config struct {
	Url      string
	Name     string
	Password string
}

func init() {
	GlobalConfig.Store(&config{
		Url:      strconv.Itoa(count),
		Name:     strconv.Itoa(count),
		Password: "<REDACTED>",
	})
}

func updateConfig(count int) {
	GlobalConfig.Store(&config{
		Url:      strconv.Itoa(count),
		Name:     strconv.Itoa(count),
		Password: "<REDACTED>",
	})
	updateSignal <- 1
}

func loadConfig() *config {
	return GlobalConfig.Load().(*config)
}

func TestAtomic1(t *testing.T) {
	// Background goroutine updates the configuration
	go func() {
		for {
			time.Sleep(3 * time.Second)
			count++
			updateConfig(count)
		}
	}()

	// Main goroutine pulls the latest configuration and processes it
	for {
		select {
		case <-updateSignal:
			fmt.Println(loadConfig())
			//...
		}
	}

}

4. Source Code Analysis

4.1. Data Structure

type Value struct {
  v interface{}// can store a value of any type
}

// Used to split interface{} into its two fields
type ifaceWords struct {
  typ  unsafe.Pointer
  data unsafe.Pointer
}

4.2. Methods

4.2.1. Store

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 of vp
  xp := (*ifaceWords)(unsafe.Pointer(&x)) // new value of xp
  // Infinite loop
  for {
    typ := LoadPointer(&vp.typ)// get the dynamic type of the old value
    if typ == nil {// the dynamic type is empty, so this is the first initialization

      runtime_procPin()
      // CAS sets the dynamic type of the old value to 111...111
      if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
        runtime_procUnpin()
        continue// continue the for loop if the set failed
      }
      // Set the dynamic value and dynamic type
      StorePointer(&vp.data, xp.data)
      StorePointer(&vp.typ, xp.typ)
      runtime_procUnpin()
      return// return directly after the first set succeeds
    }
    // If the dynamic type is 11111...11111, another goroutine is performing the first set
    if uintptr(typ) == ^uintptr(0) {
      // Continue the for loop until the first set succeeds
      continue
    }
    // Check whether the old and new values have the same dynamic type; panic if they differ
    if typ != xp.typ {
      panic("sync/atomic: store of inconsistently typed value into Value")
    }
    // Set the dynamic value
    StorePointer(&vp.data, xp.data)
    return
  }
}

StorePointer and LoadPointer are atomic operations. The key is that the first operation needs to set the dynamic type and set the dynamic value. These are two steps, so a lock is needed to guarantee atomicity. Here, CAS sets the dynamic type to unsafe.Pointer(^uintptr(0)) to act as the lock. The second operation only needs to set the dynamic value, so it no longer needs to CAS the dynamic type to unsafe.Pointer(^uintptr(0)) as a lock.

4.2.2. Load

func (v *Value) Load() (x interface{}) {
  vp := (*ifaceWords)(unsafe.Pointer(v))
  // Get the dynamic type of the old value
  typ := LoadPointer(&vp.typ)
  // If the dynamic type is empty, the first set has not happened yet, so return nil
  if typ == nil || uintptr(typ) == ^uintptr(0) {
    return nil
  }
  // Get the dynamic value
  data := LoadPointer(&vp.data)
  // Assign the dynamic value and dynamic type to the new variable xp
  xp := (*ifaceWords)(unsafe.Pointer(&x))
  xp.typ = typ
  xp.data = data
  return
}

5. References

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub