← Все этапы
05 · КОНКУРЕНТНОСТЬУрок 32 из 3322 минуты

Mutex и WaitGroup

Защищаем общие данные от одновременных изменений и ждём окончания всех работников.

Дождаться и защитить

Если несколько горутин читают и меняют одну переменную или карту одновременно, итог может зависеть от расписания. Для такого неконтролируемого общего доступа возникает гонка данных — это ошибка программы, даже если несколько запусков случайно дали ожидаемый ответ.

sync.Mutex даёт взаимное исключение: между Lock и Unlock критическую секцию выполняет только один владелец блокировки. Освобождай mutex сразу после защищённой операции; если из критической секции возможен ранний выход, отложи Unlock через defer. Удерживай блокировку только вокруг общего состояния.

sync.WaitGroup считает незавершённые задачи. Вызови Add до запуска горутины, а внутри каждой вызови Done, обычно отложенно. Wait не возвращается, пока счётчик не станет нулём. Она гарантирует, что работники закончили, но сама по себе не защищает данные.

Пример

package main

import (
	"fmt"
	"sort"
	"sync"
)

func main() {
	counts := make(map[string]int)
	var mu sync.Mutex
	var workers sync.WaitGroup

	for start := 1; start <= 100; start += 25 {
		end := start + 25
		if end > 101 {
			end = 101
		}
		workers.Add(1)
		go func(start, end int) {
			defer workers.Done()
			for number := start; number < end; number++ {
				key := "нечётные"
				if number%2 == 0 {
					key = "чётные"
				}
				mu.Lock()
				counts[key]++
				mu.Unlock()
			}
		}(start, end)
	}

	workers.Wait()
	keys := []string{"нечётные", "чётные"}
	sort.Strings(keys)
	for _, key := range keys {
		fmt.Printf("%s: %d\n", key, counts[key])
	}
}

Каждый работник получает собственный диапазон и меняет общую карту под mutex. Перед чтением результатов основная функция вызывает Wait, поэтому значения уже готовы. Порядок ключей задан явно, а число чётных и нечётных среди 1–100 всегда равно 50.

Блокировка должна защищать все одновременные обращения к этой карте, включая чтение. Здесь чтение начинается только после Wait, которая служит границей завершения работников. Не заменяй WaitGroup задержкой: время выполнения горутины не является сигналом о завершении.

Практика

Попробуйте сами

Раздели числа от 1 до 60 между тремя горутинами. Каждая увеличивает общий счётчик чисел, кратных трём, под mutex. Дождись всех работников через WaitGroup и выведи счётчик.

Проверка результата

Как понять, что получилось

Результат всегда равен 20. Добавление задачи в WaitGroup происходит до запуска соответствующей горутины, каждая горутина вызывает Done, а чтение итогового счётчика выполняется после Wait.

Mutex и WaitGroup | Go | WebSchool · Go