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

`sync.Mutex` і `sync.WaitGroup`

Канали є основною ідіомою Go, але іноді більш традиційною потрібні примітиви. Пакет 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
}

Лише одна gor-програма за раз входить між 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 gor-програм

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 (перед запуском goroutines).
  2. Після завершення кожна gor-програма викликає 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 виконується рівно один раз, навіть під час одночасного дзвінки. Шаблон ініціалізації Singleton/lady.

Mutex проти каналу: коли що використовувати

Інструкції (також з офіційних поширених запитань Go):

СправаБажаний інструмент
Передача права власності на даніканал
Розподіл роботи (черга роботи)канал
Координація незалежних підпрограммканал
Захист поля структуриMutex
Кеш/лічильникMutex або атомарний
Посилання на Singletonsync.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()).

Завантаження редактора…
Показати підказку

Додайте ПЕРЕД запуском goroutine; Виконано ВСЕРЕДИНІ goroutine з відкладенням.

Рішення доступне після 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 для виявлення перегонів даних.