1. Atomic operations in Go

Atomicity: The ability of a CPU to execute one or more operations without interruption, called atomicity. These operations appear to the outside world as an integral whole, and they are either all performed or none performed. The outside world does not see that they are only half-performed.

Atomic operations: operations that cannot be interrupted during operations. Atomic operations are supported by the underlying hardware, whereas locks are implemented by apis provided by the operating system, which are usually more efficient if the same functionality is implemented

Minimum case:

package main

import (
	"sync"
	"fmt"
)

var count int

func add(wg *sync.WaitGroup) {
	defer wg.Done()
	count++
}

func main(a) {
	wg := sync.WaitGroup{}
	wg.Add(1000)
	for i := 0; i < 1000; i++ {
		go add(&wg)
	}
	wg.Wait()
	fmt.Println(count)
}
Copy the code

Count cannot equal 1000, because count++ is actually three operations:

  • Read from memorycount
  • CPU updatecount = count + 1
  • writecountTo the memory

As a result, multiple Goroutines read the same value and then update the same value to memory, resulting in a smaller result than expected

2. Sync/Atomic package in Go

The atomic operations provided by the Go language are non-invasive and are represented by a number of functions in sync/ AOtomic in the standard library

There are six types supported in the Atomic package

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

For each type, five classes of atomic operations are provided:

  • LoadXXX(addr): acquisition of atomicity*addrIs equivalent to:
    return *addr
    Copy the code
  • StoreXXX(addr, val): atomic willvalSave the value to*addr, is equivalent to:
    addr = val
    Copy the code
  • AddXXX(addr, delta): atomic willdeltaAdd the value to*addrAnd returns the new value (unsafe.PointerNot supported), equivalent to:
    *addr += delta
    return *addr
    Copy the code
  • SwapXXX(addr, new) old: atomic willnewSave the value to*addrAnd return the old value, equivalent to:
    old = *addr
    *addr = new
    return old
    Copy the code
  • CompareAndSwapXXX(addr, old, new) bool: atomic comparison*addrandold, if they are the samenewAssigned to*addrAnd returntrue, is equivalent to:
    if *addr == old {
        *addr = new
        return true
    }
    return false
    Copy the code

Therefore, the first part of the case can be modified as follows, can pass

// Change method 1
func add(wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		if atomic.CompareAndSwapInt32(&count, count, count+1) {
			break}}}// Change mode 2
func add(wg *sync.WaitGroup) {
	defer wg.Done()
	atomic.AddInt32(&count, 1)}Copy the code

3. Expand the scope of atomic operations: atomic.value

In version 1.4, the Go language added a new type Value to the Sync/Atomic package, which acts as a container for storing and loading values of any type “atomically”

  • type Value
    • Func (v *Value) Load() (x interface{}): read from thread-safe v
    • Func (v *Value) Store(x interface{}): write operation to Store original variable xatomic.ValueType of v

For example, the author was 22 when he wrote the article, and then 23 when he wrote it…

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main(a) {
	// The simple data type is still used here because of the small amount of code
	config := atomic.Value{}
	config.Store(22)

	wg := sync.WaitGroup{}
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func(i int) {
			defer wg.Done()
			// Change the configuration in a goroutine
			if i == 0 {
				config.Store(23)}// the output contains inclusion 22,23
			fmt.Println(config.Load())
		}(i)
	}
	wg.Wait()
}
Copy the code

4. Atomic.Value source code analysis

Atomic.Value is designed to store arbitrary types of data, so its internal field is of type interface{}

type Value struct {
	v interface{}}Copy the code

There is also an ifaceWords type that serves as an internal representation for an empty interface, with TYP representing the primitive type and Data representing the real value

// ifaceWords is interface{} internal representation.
type ifaceWords struct {
	typ  unsafe.Pointer
	data unsafe.Pointer
}
Copy the code

4.1 the unsafe. Pointer

The Go language does not support directly manipulating memory, but the library does provide a backward-compatible Pointer type, unsafe.Pointer, that allows applications to manipulate memory flexibly, bypashing the Go type system

In other words, if two types have the same memory structure, we can use unsafe.Pointer as a bridge to convert the two types of Pointers to each other, thus allowing the same memory to be read in two ways

For example, int and int32 have the same internal storage structure, but we need to do this for pointer conversions:

var a int32
// Get a *int pointer to a
(*int)(unsafe.Pointer(&a))
Copy the code

4.2 Atomic read arbitrary structure operation

func (v *Value) Load(a) (x interface{}) {
    // Convert *Value pointer type to *ifaceWords pointer type
	vp := (*ifaceWords)(unsafe.Pointer(v))
	// Atomically get a pointer to typ of v
	typ := LoadPointer(&vp.typ)
	// If there is no writing or is writing, return the uintptr(0) to represent the transition state, as described below
	if typ == nil || uintptr(typ) == ^uintptr(0) {
		return nil
	}
	// Atomically get a pointer to v's real value data and return it
	data := LoadPointer(&vp.data)
	xp := (*ifaceWords)(unsafe.Pointer(&x))
	xp.typ = typ
	xp.data = data
	return
}
Copy the code

4.3 Atomic storage of arbitrary structure operation

There is an important piece of code before this, in which the runtime_procPin method can take a Goroutine to the current P (see goroutine scheduler here) : No other goroutine is allowed to preempt, and runtime_procUnpin is the release method

// Disable/enable preemption, implemented in runtime.
func runtime_procPin(a)
func runtime_procUnpin(a)
Copy the code

Store method

func (v *Value) Store(x interface{}) {
	if x == nil {
		panic("sync/atomic: store of nil value into Value")}// Convert the existing value and the value to be written to type ifaceWords, so that the next step can get their original type and the real value
	vp := (*ifaceWords)(unsafe.Pointer(v))
	xp := (*ifaceWords)(unsafe.Pointer(&x))
	for {
		// Get the type of the existing value
		typ := LoadPointer(&vp.typ)
		// If typ is nil, this is the first Store
		if typ == nil {
			// If this is your first time, try to hold on to the current processor and not allow any other Goroutine to grab it
			runtime_procPin()
			// Using CAS, try setting typ to ^uintptr(0) as an intermediate state
			// If the assignment fails, another thread has completed the assignment first
			// It unlocks the preemption lock and returns to the first step of the for loop
			if! CompareAndSwapPointer(&vp.typ,nil, unsafe.Pointer(^uintptr(0))) {
				runtime_procUnpin()
				continue
			}
			// If the setting succeeds, jackpot is currently in goroutine
			// Then update the corresponding pointer atomically, and finally release the preemption lock
			StorePointer(&vp.data, xp.data)
			StorePointer(&vp.typ, xp.typ)
			runtime_procUnpin()
			return
		}
		// If the value of typ is ^uintptr(0), the first write has not been completed
		if uintptr(typ) == ^uintptr(0) {
			continue
		}
		// If the type to be written is inconsistent with an existing type, panic occurs
		iftyp ! = xp.typ {panic("sync/atomic: store of inconsistently typed value into Value")}/ / update the data
		StorePointer(&vp.data, xp.data)
		return}}Copy the code

Reference 5.

Go Sync/Atomic official documentation

The atomic operations sync/atomic in Go

The history of atomic.Value in the Go language standard library

How is sync.pool optimized in Go 1.13?