Go за Прикладом: Атомарні Лічильники

Як ми вже дізнались, основним механізмом роботи з асинхронністю в Go є, комунікація за допомогою каналів. Ми побачили це на на прикладі “пулу працівників” та у інших випадках. Але існують і інші способи управління станом та асинхронністю, наприклад, зараз ми розглянемо пакет sync/atomic для атомарних лічильників, доступ до яких є у кількох горутин.

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

Ось ми використаємо беззнакове ціле для представлення нашого (завжди додатнього) лічильника.

    var ops uint64

Для симуляції одночасних оновлень, ми запускаємо 50 горутин, кожна з яких збільшуватиме лічильник на одиницю що мілісекунди.

    for i := 0; i < 50; i++ {
        go func() {
            for {

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

                atomic.AddUint64(&ops, 1)

Мілісекунда паузи в роботі.

                time.Sleep(time.Millisecond)
            }
        }()
    }

Чекаємо одну 1 секунду, надаючи змогу ops акумулювати деяке значення.

    time.Sleep(time.Second)

Для того, щоб безпечно використовувати лічильник який все ще оновлюється іншими горутинами, ми отримуємо копію значення за допомогою LoadUint64 і передаємо її opsFinalю Так само: як і в попередньому прикладі, нам потрібно надати вказівник на комірку пам’яті, щоб функція LoadUint64 могла зняти копію цього значення.

    opsFinal := atomic.LoadUint64(&ops)
    fmt.Println("ops:", opsFinal)
}

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

$ go run atomic-counters.go
ops: 50000

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

Наступний приклад: Групи Очікування (WaitGroups).