Доступ к данным в параллельном режиме с использованием мьютексов

Beginner

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

Введение

Этот лабораторный практикум旨在演示如何使用мьютексы для безопасного доступа к данным из нескольких goroutine.

Mutexes

Проблема, которую необходимо решить в этом лабораторном практикуме, заключается в том, чтобы увеличивать именованный счетчик в цикле с использованием нескольких goroutine и обеспечить синхронизацию доступа к счетчику.

  • Используйте структуру Container, чтобы хранить карту счетчиков.
  • Используйте Mutex, чтобы синхронизировать доступ к карте counters.
  • Структура Container должна иметь метод inc, который принимает строку name и увеличивает соответствующий счетчик в карте counters.
  • Метод inc должен заблокировать мьютекс перед доступом к карте counters и разблокировать его в конце функции с использованием инструкции defer.
  • Используйте структуру sync.WaitGroup, чтобы дождаться завершения goroutine.
  • Используйте функцию fmt.Println, чтобы вывести карту counters.
## Запуск программы показывает, что счетчики
## обновляются как ожидается.

## Далее мы рассмотрим реализацию этой же задачи
## управления состоянием, используя только goroutine и каналы.

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

// В предыдущем примере мы увидели, как управлять простым
// состоянием счетчика с использованием [атомарных операций](atomic-counters).
// Для более сложного состояния мы можем использовать [_мьютекс_](https://en.wikipedia.org/wiki/Mutual_exclusion)
// для безопасного доступа к данным из нескольких goroutine.

package main

import (
    "fmt"
    "sync"
)

// Container хранит карту счетчиков; так как мы хотим
// обновлять ее одновременно из нескольких goroutine, мы
// добавляем `Mutex`, чтобы синхронизировать доступ.
// Обратите внимание, что мьютексы не должны копироваться, поэтому если эта
// `структура` передается по ссылке, это должно быть сделано
// указателем.
type Container struct {
    mu       sync.Mutex
    counters map[string]int
}

func (c *Container) inc(name string) {
    // Заблокируйте мьютекс перед доступом к `counters`; разблокируйте
    // его в конце функции с использованием инструкции [defer](defer).
    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()
    }

    // Запустите несколько goroutine одновременно; обратите
    // внимание, что они все обращаются к одному и тому же `Container`,
    // и два из них обращаются к одному и тому же счетчику.
    wg.Add(3)
    go doIncrement("a", 10000)
    go doIncrement("a", 10000)
    go doIncrement("b", 10000)

    // Подождите, пока goroutine завершатся
    wg.Wait()
    fmt.Println(c.counters)
}

Резюме

В этом лабораторном практикуме мы узнали, как использовать мьютексы для безопасного доступа к данным из нескольких goroutine. Мы создали структуру Container, чтобы хранить карту счетчиков, и использовали Mutex, чтобы синхронизировать доступ к карте counters. Мы также реализовали метод inc, чтобы увеличить именованный счетчик, и использовали структуру sync.WaitGroup, чтобы дождаться завершения goroutine. Наконец, мы вывели карту counters с использованием функции fmt.Println.