The introduction

It is not safe to access external variables in a Goroutine, so let’s look at the following example. We do a count, use the sync.waitgroup package to ensure that all 1000 goroutines we created have been executed, then print n, and run the program to see what happens:

package main

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

func main(a) {
    var n int32
    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func(a) {
            n = n + 1
            wg.Done()
        }()
    }
    wg.Wait()

    fmt.Println(n) // The result of each execution is not 1000, less than or equal to 1000 value fluctuation},Copy the code

In the case of multi-core computer, the CPU is not limited to execute the above code, the result of each execution is not 1000, but less than or equal to 1000 numerical fluctuations. Why does this happen? Above we created 1000 goroutines through the for loop. Each goroutine was added with n plus 1 to add two of the goroutines as follows:

  1. Goroutine1 reads the variable n with a value of 800
  2. Goroutine2 reads the variable n with a value of 800
  3. Goroutine1 Runs n+1, and n changes to 801
  4. Goroutine2 Run n+1, and the n changes to 801
  5. Two goroutine executions completed
  6. So we added one less to n, so we ended up with one less than we expected

Use sync/atomic to modify the above code

We use the SYNC /atomic atomic operation to resolve the above concurrency safety issues as follows:


func main(a) {
    var n int32
    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func(a) {
            atomic.AddInt32(&n, 1)  
            wg.Done()
        }()
    }
    wg.Wait()

    fmt.Println(atomic.LoadInt32(&n)) / / 1000},Copy the code

We use atomic.AddInt32 to add values to the package, and finally output the expected result. In fact, the method in the package will not be interrupted by other tasks or events until the completion of execution. If another Goroutine tries to read or write, it blocks until the atomic operation is complete.

There are many types of atomic manipulation support available in Sync/Atomic, with five main categories:

  1. Load: This method is responsible for getting the corresponding value from the corresponding memory address
  2. Store: This class is responsible for storing the corresponding value in the corresponding memory address
  3. Add: This method can be understood as a combination of Load and Store, i.e. Load first and then Add
  4. Swap: This method can be understood as a Load, Store the new value, and then return the old value
  5. CompareAndSwap: This method compares the value of the old data with the value of the stored data in the address. If they are the same, execute Swap, store the new value in the address and return true. If they are different, return false

Commonly used method

The methods in the sync/atomic package are of the following format:

func AddT(addr *T, delta T)(new T)
func LoadT(addr *T) (val T)
func StoreT(addr *T, val T)
func SwapT(addr *T, new T) (old T)
func CompareAndSwapT(addr *T, old, new T) (swapped bool)
Copy the code

Such as:

// The following is for in32
func AddInt32(addr *int32, delta int32)(new int32)
func LoadInt32(addr *int32) (val int32)
func StoreInt32(addr *int32, val int32)
func SwapInt32(addr *int32.new int32) (old int32)
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)

// Provide the following four atomic functions for the (safe) pointer type:
// There is no AddPointer because Pointers to go do not support arithmetic operations
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
func SwapPointer(addr *unsafe.Pointer, new T) (old unsafe.Pointer)
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
Copy the code