【Golang】使用 - Atomic Counters

Posted by 西维蜀黍 on 2021-07-31, Last Modified on 2021-10-17

Use Build-in Funtions

Atomic Operations for Integers

package main

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

func main() {

    var ops uint64

    var wg sync.WaitGroup

    for i := 0; i < 50; i++ {
        wg.Add(1)

        go func() {
            for c := 0; c < 1000; c++ {

                atomic.AddUint64(&ops, 1)
            }
            wg.Done()
        }()
    }

    wg.Wait()

    fmt.Println("ops:", ops)
}

Atomic Operations for Pointers

In Go, values of any pointer type can be explicitly converted to unsafe.Pointer, and vice versa. So values of *unsafe.Pointer type can also be explicitly converted to unsafe.Pointer, and vice versa.

The following example is not a concurrent program. It just shows how to do atomic pointer operations. In this example, T can be an arbitrary type.

package main

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

type T struct {x int}
var pT *T

func main() {
	var unsafePPT = (*unsafe.Pointer)(unsafe.Pointer(&pT))
	var ta, tb = T{1}, T{2}
	// store
	atomic.StorePointer(
		unsafePPT, unsafe.Pointer(&ta))
	fmt.Println(pT) // &{1}
	// load
	pa1 := (*T)(atomic.LoadPointer(unsafePPT))
	fmt.Println(pa1 == &ta) // true
	// swap
	pa2 := atomic.SwapPointer(
		unsafePPT, unsafe.Pointer(&tb))
	fmt.Println((*T)(pa2) == &ta) // true
	fmt.Println(pT) // &{2}
	// compare and swap
	b := atomic.CompareAndSwapPointer(
		unsafePPT, pa2, unsafe.Pointer(&tb))
	fmt.Println(b) // false
	b = atomic.CompareAndSwapPointer(
		unsafePPT, unsafe.Pointer(&tb), pa2)
	fmt.Println(b) // true
}

Yes, it is quite verbose to use the pointer atomic functions. In fact, not only are the uses verbose, they are also not protected by the Go 1 compatibility guidelines, for these uses require to import the unsafe standard package.

Personally, I think the possibility is small that the legal pointer value atomic operations used in the above example will become illegal later. Even if they become illegal later, the go fix command provided in Go Toolchain should fix them with a later alternative new legal way. But, this is just my opinion, which is not authoritative.

If you do worry about the future legality of the pointer atomic operations used in the above example, you can use the atomic operations introduced in the next section for pointers, though the to be introduced operations are less efficient than the ones introduced in the current section.

Use Value

Atomic Operations for Values of Arbitrary Types

The following example shows how to use Value for periodic program config updates and propagation of the changes to worker goroutines.

package main

import (
	"sync/atomic"
	"time"
)

func loadConfig() map[string]string {
	return make(map[string]string)
}

func requests() chan int {
	return make(chan int)
}

func main() {
	var config atomic.Value // holds current server configuration
	// Create initial config value and store into config.
	config.Store(loadConfig())
	go func() {
		// Reload config every 10 seconds
		// and update config value with the new version.
		for {
			time.Sleep(10 * time.Second)
			config.Store(loadConfig())
		}
	}()
	// Create worker goroutines that handle incoming requests
	// using the latest config value.
	for i := 0; i < 10; i++ {
		go func() {
			for r := range requests() {
				c := config.Load()
				// Handle request r using config c.
				_, _ = r, c
			}
		}()
	}
}

The following example shows how to maintain a scalable frequently read, but infrequently updated data structure using copy-on-write idiom.

package main

import (
	"sync"
	"sync/atomic"
)

func main() {
	type Map map[string]string
	var m atomic.Value
	m.Store(make(Map))
	var mu sync.Mutex // used only by writers
	// read function can be used to read the data without further synchronization
	read := func(key string) (val string) {
		m1 := m.Load().(Map)
		return m1[key]
	}
	// insert function can be used to update the data without further synchronization
	insert := func(key, val string) {
		mu.Lock() // synchronize with other potential writers
		defer mu.Unlock()
		m1 := m.Load().(Map) // load current value of the data structure
		m2 := make(Map)      // create a new value
		for k, v := range m1 {
			m2[k] = v // copy all data from the current object to the new one
		}
		m2[key] = val // do the update that we need
		m.Store(m2)   // atomically replace the current object with the new one
		// At this point all new readers start working with the new version.
		// The old version will be garbage collected once the existing readers
		// (if any) are done with it.
	}
	_, _ = read, insert
}

Reference