Go за Прикладом: Взаємні Виключення (Mutexes)

У попередньому прикладі ми розглянули як керувати простим лічильником за допомогою атомарних лічильників. У більш складних випадках ми можемо скористатись м’ютексами для безпечного доступу до даних в горутинах.

package main
import (
    "fmt"
    "sync"
)

Контейнер вміщую мапу лічильників і ми хочемо оновлювати їх одночасно з різних горутин. Ми додаємо мутекс щоб синхронізувати доступ. зауважте що мутекси не повинні бути скопійовані, тому якщо вам потрібно передавати мутекс туди-сюди скористайтесь вказівником.

type Container struct {
    mu       sync.Mutex
    counters map[string]int
}

Замикаємо мутекс перед доступом до лічильників; відмикаємо за допомогою відкладеного виклику перед виходом з функції.

func (c *Container) inc(name string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.counters[name]++
}

Можливо використовувати нульове значення мутекса без ініціалізації.

func main() {
    c := Container{
        counters: map[string]int{"a": 0, "b": 0},
    }
    var wg sync.WaitGroup

Ця функція в циклі інкрементує іменний лічильник.

    doIncrement := func(name string, n int) {
        for i := 0; i < n; i++ {
            c.inc(name)
        }
        wg.Done()
    }

Запустимо кілька горутин одночасно; Зауважте що вони мають доступ до самого Контейнеру, та дві з них мають доступ до того самого лічильника..

    wg.Add(3)
    go doIncrement("a", 10000)
    go doIncrement("a", 10000)
    go doIncrement("b", 10000)

Очукуємо завершення горутин

    wg.Wait()
    fmt.Println(c.counters)
}

Запуск прогрмми покаже що лічильники оновлені як і планувалось.

$ go run mutexes.go
map[a:20000 b:10000]

Далі ми розглянемо як досягти тогож використовуючи лише канали та горутини.

Наступний приклад: Stateful Goroutines.