Перейти к основному содержимому
Модуль 7 · Урок 4 из 534/50 в курсе~14 min
Уроки модуля (4/5)

`sync.Mutex` и `sync.WaitGroup`

Каналы — основная идиома Го, но иногда они более традиционны. нужны примитивы. Пакет sync предоставляет мьютексы, подождите. группы и другие утилиты для координации общего состояния.

sync.Mutex: эксклюзивная блокировка

Go
import "sync"

type Counter struct {
    mu    sync.Mutex
    count int
}

func (c *Counter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

func (c *Counter) Get() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

Между Lock() и Unlock() одновременно может находиться только одна горутина. Обязательный шаблон: defer mu.Unlock() сразу после Lock().

sync.RWMutex: много читателей, один писатель

Для состояния тяжелого чтения:

Go
var mu sync.RWMutex

mu.RLock()              // read lock: più goroutine simultanee OK
v := data
mu.RUnlock()

mu.Lock()               // write lock: esclusivo
data = newValue
mu.Unlock()

Полезно только в том случае, если чтение действительно доминирует над записью; в остальном обычный sync.Mutex проще и зачастую быстрее благодаря меньшим внутренним раздор.

sync.WaitGroup: дождаться N горутин

Go
var wg sync.WaitGroup

for i := 0; i < 3; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        work(id)
    }(i)
}
wg.Wait()    // blocca finché tutti i Done() sono arrivati

Рабочий процесс:

  1. wg.Add(N) увеличивает счетчик на N (перед запуском горутин).
  2. Каждая горутина вызывает wg.Done() после завершения (в идеале через defer).
  3. wg.Wait() блокируется до тех пор, пока счетчик не вернется к 0.

sync.Once: потокобезопасная ленивая инициализация

Go
var (
    once   sync.Once
    config *Config
)

func GetConfig() *Config {
    once.Do(func() {
        config = loadConfig()
    })
    return config
}

Обратный вызов в once.Do выполняется ровно один раз, даже при одновременном звонки. Шаблон синглтона/ленивой инициализации.

Мьютекс против канала: когда что использовать

Рекомендации (также из официального FAQ по Go):

ДелоПредпочтительный инструмент
Передача права собственности на данныеканал
Распределение работы (очередь работ)канал
Координация независимых горутинканал
Защита поля структурыМьютекс
Кэш/счетчикМьютекс или атомарный
Ссылка на синглтонsync.Once

«Каналы для оркестрации, мьютексы для общих данных» — хорошее практическое правило.

Детектор гонок

Запустите тесты с -race, чтобы обнаружить гонки данных:

Bash
go test -race ./...
go run -race main.go

Детектор гонок анализирует двоичный файл и регистрирует все несинхронизированные доступ к общей памяти. Это инструмент №1 для проверки параллельного кода. перед производством.

Попробуйте это

Упражнение#go.m7.l4.e1
Попыток: 0Загрузка…

Защитите приращение счетчика с помощью mu.Lock() + defer mu.Unlock().

Загрузка редактора…
Показать подсказку

defer mu.Unlock() сразу после Lock() гарантирует освобождение даже в случае паники.

Решение доступно после 3 попыток

Упражнение#go.m7.l4.e2
Попыток: 0Загрузка…

Запустите 3 горутины и дождитесь их всех, используя sync.WaitGroup (Add(3), Done() в каждой горутине, Wait()).

Загрузка редактора…
Показать подсказку

Добавьте ПЕРЕД запуском горутины; Выполнено ВНУТРИ горутины с отсрочкой.

Решение доступно после 3 попыток

Quiz#go.m7.l4.e3
Готово

Какой рекомендуемый шаблон гарантирует освобождение мьютекса?

Go
mu.Lock()
// ?
Варианты ответа

Резюме

  • sync.Mutex защищает общее состояние; всегда defer mu.Unlock().
  • Используйте приемник указателя, чтобы избежать копирования мьютекса.
  • sync.RWMutex для шаблонов «много читателей/одна запись» (только если это помогает).
  • sync.WaitGroup: Add перед go, Done с defer, Wait ждать.
  • sync.Once: потокобезопасная ленивая инициализация.
  • «Каналы для оркестрации, мьютексы для общих данных».
  • go test -race: инструмент №1 для выявления конфликтов данных.