Что произойдет при одновременной записи в `map` из нескольких горутин без синхронизации?

Ответ

Одновременная запись в map из нескольких горутин без внешних механизмов синхронизации приведет к состоянию гонки (data race).

Тип map в Go не является потокобезопасным для записи. Последствия состояния гонки могут быть следующими:

  • Паника (panic): Наиболее вероятный исход. Среда выполнения Go содержит встроенный детектор гонок для map и аварийно завершит программу с ошибкой fatal error: concurrent map writes.
  • Повреждение данных: Внутренняя структура map может быть повреждена, что приведет к потере данных или несогласованному состоянию.
  • Непредсказуемое поведение: Программа может продолжить работу, но с неверными данными в map.

Пример опасного кода:

m := make(map[int]int)
wg := sync.WaitGroup{}

for i := 0; i < 10; i++ {
    wg.Add(1)
    go func(i int) {
        defer wg.Done()
        m[i] = i // Опасно! Возникнет паника.
    }(i)
}
wg.Wait()

Как решить проблему?

Для безопасной работы с map в конкурентной среде необходимо использовать примитивы синхронизации:

  1. sync.Mutex: Самый распространенный способ. Блокировка защищает map на время операции записи (или чтения).

    var (
        m  = make(map[int]int)
        mu sync.Mutex
    )
    
    mu.Lock()   // Захватываем блокировку
    m[key] = value
    mu.Unlock() // Освобождаем
  2. sync.RWMutex: Более производительный вариант, если количество чтений значительно превышает количество записей. Позволяет множеству горутин читать одновременно.

    var rwMu sync.RWMutex
    // Для записи
    rwMu.Lock()
    m[key] = value
    rwMu.Unlock()
    
    // Для чтения
    rwMu.RLock()
    _ = m[key]
    rwMu.RUnlock()
  3. sync.Map: Специализированный тип, оптимизированный для сценариев, когда ключ записывается один раз, а затем многократно читается, или когда разные горутины работают с разными наборами ключей. Имеет другой API (Store, Load, Delete).