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 i := 0; i < 50; i++ {
        wg.Add(1)

To atomically increment the counter we use Add.

        go func() {
            for c := 0; c < 1000; c++ {
                ops.Add(1)
            }
            wg.Done()
        }()
    }

Чекаємо поки усі горутини не відпрацюють.

    wg.Wait()

Жодна горутина не записує до ‘ops’, але використаємо Load для безпечного читання значення (навіть тоді коли інши горутини його оновлюють).

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

Ми очікувано отримали рівно 50000 операцій. Як би ми використали не атомарний ‘ops++’ числа б - були інші. Ми доречі можемо отримувати дані про стан гонки даних коли запускаємо з прапорцем -race.

$ go run atomic-counters.go
ops: 50000

Надалі ми ознайомимось з mutexeами, ще одниx з інструментів Go - призначення якого управління станом.

Наступний приклад: Взаємні Виключення (Mutexes).