Дождаться и защитить
Если несколько горутин читают и меняют одну переменную или карту одновременно, итог может зависеть от расписания. Для такого неконтролируемого общего доступа возникает гонка данных — это ошибка программы, даже если несколько запусков случайно дали ожидаемый ответ.
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 задержкой: время выполнения горутины не является сигналом о завершении.