> Являются ли мапы в Go потокобезопасными (Go)

Уровень: senior · Роль: backend · Категория: Технические вопросы

Компании: ЦУМ, InDrive

Стек: Go

> Пример ответа

Короткий ответ

Нет, встроенные мапы в Go не являются потокобезопасными. Одновременное чтение и запись в одну и ту же мапу из нескольких горутин приводит к data race и может вызвать панику с сообщением fatal error: concurrent map writes или concurrent map read and map write. Для безопасного использования требуется внешняя синхронизация: мьютексы, sync.RWMutex или sync.Map для специфичных сценариев.

Подробное объяснение

Мапа в Go - это хеш-таблица с сложной внутренней структурой: bucket'ы, overflow-цепочки, поля для подсчёта элементов и флаги состояния. При записи происходит не только изменение значения, но и потенциальный рехешинг, рост таблицы, изменение указателей. Если две горутины одновременно модифицируют структуру мапы, внутреннее состояние может быть повреждено - отсюда и жёсткая детекция на уровне рантайма.

Важно понимать: рантайм Go детектирует конкурентный доступ не всегда, а только когда есть одновременные операции, которые могут конфликтовать. В некоторых случаях data race может остаться незамеченным, но результат будет непредсказуемым - потерянные записи, некорректные чтения, бесконечные циклы при итерации.

Отдельно стоит отметить: чтение из мапы, пока другая горутина пишет в неё, тоже небезопасно. Даже если вы только читаете, рантайм может зафиксировать concurrent map read and map write. Это связано с тем, что при записи мапа может перестраиваться, и читатель может увидеть несогласованное состояние.

sync.Map - это отдельная реализация, оптимизированная для двух сценариев: когда ключи стабильны и редко обновляются, и когда разные горутины работают с разными ключами. Она использует атомарные операции и амортизированную запись, но не является универсальной заменой обычной мапы с мьютексом.

На практике

Для большинства случаев используйте обычную мапу с sync.RWMutex: это даёт читателям конкурентный доступ, а писателям - эксклюзивный. sync.Map оправдан только если у вас есть профилирование, показывающее, что мьютекс стал узким местом, и паттерн доступа подходит под его оптимизации.

Также помните: итерация по мапе во время записи из другой горутины - это тоже data race. Даже если вы используете range, рантайм не предоставляет снапшот - итерация идёт по живой структуре.

Пример кода

GO
// Небезопасно: data race
func unsafeIncrement(m map[string]int, key string, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 1000; i++ {
m[key]++
}
}
// Безопасно: sync.RWMutex
type SafeMap struct {
mu sync.RWMutex
m map[string]int
}
func (s *SafeMap) Inc(key string) {
s.mu.Lock()
defer s.mu.Unlock()
s.m[key]++
}
func (s *SafeMap) Get(key string) int {
s.mu.RLock()
defer s.mu.RUnlock()
return s.m[key]
}
// Безопасно: sync.Map
var sm sync.Map
sm.Store("counter", 0)
// Атомарное обновление через LoadOrStore + CompareAndSwap
for {
val, _ := sm.Load("counter")
if sm.CompareAndSwap("counter", val, val.(int)+1) {
break
}
}

Как отвечать на собеседовании

Начните с прямого ответа: нет, не потокобезопасны. Затем объясните, почему: внутренняя структура мапы изменяется при записи, рантайм детектирует конкурентный доступ и паникует. Упомяните, что даже чтение во время записи небезопасно. После этого предложите решения: sync.RWMutex как основной вариант, sync.Map для специфичных сценариев. Если спросят про sync.Map - объясните, когда она оправдана, а когда нет.

Что проверяет интервьюер

Интервьюер проверяет, понимаете ли вы внутреннее устройство мапы, а не просто заучили факт. Важно показать понимание, почему конкурентный доступ опасен именно на уровне структуры данных, а не только на уровне логики. Также проверяется знание инструментов синхронизации и умение выбирать между ними. Senior-уровень подразумевает, что вы можете объяснить trade-off между sync.RWMutex и sync.Map, и знаете про go test -race.

Типичные ошибки

  • Утверждение, что чтение из мапы безопасно, если никто не пишет - это верно, но часто кандидаты забывают, что "никто не пишет" должно быть гарантировано на уровне программы, а не по ощущениям.
  • Предложение использовать sync.Map как универсальное решение - это ошибка, она хуже по производительности для типичных сценариев с частыми записями.
  • Игнорирование того, что итерация по мапе - тоже операция чтения, и она небезопасна при параллельной записи.
  • Утверждение, что паника concurrent map writes возникает всегда - на самом деле рантайм может не поймать все случаи, и data race может проявиться как некорректное поведение без паники.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью