Go на примерах: Атомарные счётчики

Основной механизм управления состоянием в Go — взаимодействие через каналы. Мы видели это, например, в примере с пулом воркеров. Однако есть и другие способы управления состоянием. Здесь мы рассмотрим использование пакета sync/atomic для атомарных счётчиков, к которым обращаются несколько горутин.

package main
import (
    "fmt"
    "sync"
    "sync/atomic"
)
func main() {

Используем атомарный целочисленный тип для представления нашего (всегда положительного) счётчика.

    var ops atomic.Uint64

WaitGroup поможет нам дождаться завершения всех горутин.

    var wg sync.WaitGroup

Запустим 50 горутин, каждая из которых увеличит счётчик ровно 1000 раз.

    for range 50 {
        wg.Go(func() {
            for range 1000 {

Для атомарного увеличения счётчика используем Add.

                ops.Add(1)
            }
        })
    }

Ждём завершения всех горутин.

    wg.Wait()

Здесь ни одна горутина не пишет в ‘ops’, но с помощью Load можно безопасно атомарно читать значение, даже пока другие горутины (атомарно) его обновляют.

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

Мы ожидаем получить ровно 50 000 операций. Если бы мы использовали обычное (неатомарное) целое число и увеличивали его с помощью ops++, то, скорее всего, получили бы другое число, меняющееся между запусками, потому что горутины мешали бы друг другу. Более того, при запуске с флагом -race мы бы получили ошибки гонки данных (data race).

$ go run atomic-counters.go
ops: 50000

Далее рассмотрим мьютексы — ещё один инструмент для управления состоянием.

Далее: .