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 memory
count
- CPU update
count = count + 1
- write
count
To 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*addr
Is equivalent to:return *addr Copy the code
StoreXXX(addr, val)
: atomic willval
Save the value to*addr
, is equivalent to:addr = val Copy the code
AddXXX(addr, delta)
: atomic willdelta
Add the value to*addr
And returns the new value (unsafe.Pointer
Not supported), equivalent to:*addr += delta return *addr Copy the code
SwapXXX(addr, new) old
: atomic willnew
Save the value to*addr
And return the old value, equivalent to:old = *addr *addr = new return old Copy the code
CompareAndSwapXXX(addr, old, new) bool
: atomic comparison*addr
andold
, if they are the samenew
Assigned to*addr
And 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 x
atomic.Value
Type 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?