Atomic Counters

Атомарные счетчики (Atomic Counters)|Atomic Counters #

// Основным механизмом управления состоянием в Go является
// связь по каналам. Мы видели это, например, с [пулами воркеров](worker-pools).
// Есть несколько других вариантов управления состоянием.
// Здесь мы рассмотрим использование пакета `sync/atomic`
// для _атомарных счетчиков_, к которым обращаются горутины.

package main

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

func main() {

	// Мы будем использовать целое число без знака
	// для представления нашего (всегда положительного)
	// счетчика.
	var ops uint64

	// WaitGroup поможет нам подождать, пока все горутины
	// завершат свою работу.
	var wg sync.WaitGroup

	// Мы запустим 50 горутин, каждая из которых увеличивает
	// счетчик ровно в 1000 раз.
	for i := 0; i < 50; i++ {
		wg.Add(1)

		go func() {
			for c := 0; c < 1000; c++ {
				// Для атомарного увеличения счетчика мы
				// используем AddUint64, присваивая ему адрес
				// памяти нашего счетчика `ops` с префиксом `&`.
				atomic.AddUint64(&ops, 1)
			}
			wg.Done()
		}()
	}

	// Ждем пока завершатся горутины.
	wg.Wait()

	// Теперь доступ к `ops` безопасен, потому что мы знаем,
	// что никакие другие горутины не пишут в него. Безопасное
	// чтение атомарного счетчика во время его обновления также
	// возможно, используя функцию `atomic.LoadUint64`.
	fmt.Println("ops:", ops)
}
# Мы ожидаем получить ровно 50 000 операций. Если бы
# мы использовали неатомарный `ops++` для увеличения
# счетчика, мы бы, вероятно, получили другое число,
# изменяющееся между прогонами, потому что горутины
# мешали бы друг другу. Более того, мы получим сбои
# в гонке данных при работе с флагом -race.
$ go run atomic-counters.go
ops: 50000

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