Атомарные счётчики в конкурентном Go

Beginner

This tutorial is from open-source community. Access the source code

Введение

В этом практическом занятии мы сосредоточимся на управлении состоянием в Go с использованием пакета sync/atomic для атомарных счётчиков, доступных нескольким goroutine.

Атомарные счётчики

Задача заключается в том, чтобы увеличить счётчик ровно 1000 раз с использованием 50 goroutine и пакета sync/atomic.

  • Используйте пакет sync/atomic для увеличения счётчика.
  • Используйте WaitGroup для ожидания завершения работы всех goroutine.
## Мы ожидаем получить ровно 50 000 операций. Если бы мы
## использовали неатомарный `ops++` для увеличения счётчика,
## вероятно, мы бы получили другое число, меняющееся между
## запусками, потому что goroutine будут взаимодействовать
## друг с другом. Кроме того, мы бы получили ошибки о дата-расе
## при запуске с флагом `-race`.
$ go run atomic-counters.go
ops: 50000

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

Ниже представлен полный код:

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

package main

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

func main() {

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

    // WaitGroup поможет нам дождаться завершения работы всех
    // goroutine.
    var wg sync.WaitGroup

    // Мы запустим 50 goroutine, каждая из которых увеличит
    // счётчик ровно 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()
        }()
    }

    // Дождитесь завершения всех goroutine.
    wg.Wait()

    // Теперь безопасно получить доступ к `ops`, потому что мы
    // знаем, что никакая другая goroutine не пишет в него.
    // Безопасное чтение атомарных переменных во время их
    // обновления также возможно с использованием функций
    // типа `atomic.LoadUint64`.
    fmt.Println("ops:", ops)
}

Резюме

В этом практическом занятии мы узнали, как использовать пакет sync/atomic для управления состоянием в Go путём увеличения счётчика с использованием нескольких goroutine. Функция AddUint64 использовалась для атомарного увеличения счётчика, а WaitGroup для ожидания завершения работы всех goroutine.