Введение
Этот лабораторный практикум旨在演示如何使用мьютексы для безопасного доступа к данным из нескольких goroutine.
This tutorial is from open-source community. Access the source code
💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал
Этот лабораторный практикум旨在演示如何使用мьютексы для безопасного доступа к данным из нескольких goroutine.
Проблема, которую необходимо решить в этом лабораторном практикуме, заключается в том, чтобы увеличивать именованный счетчик в цикле с использованием нескольких 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
.