Map: устройство, ограничения и конкурентный доступ
Зачем это на интервью
Map даёт среднюю амортизированную операцию по ключу, но имеет ограничения языка и runtime. Неправильный конкурентный доступ может завершить процесс или создать data race.
Минимум для E4
- Проверять наличие ключа через
v, ok := m[k]. - Не полагаться на порядок
range. - Не читать и не писать один обычный map одновременно без синхронизации.
Углубление для E5/Senior
Map реализован runtime как хеш-таблица; детали bucket/роста не являются стабильным API. Ключ должен быть comparable; slice, map и function ключами быть не могут. Выбор между sync.Mutex, sync.RWMutex, sharding, copy-on-write и sync.Map делается по профилю чтений/записей, времени критической секции, типам ключей и измерениям. sync.Map специализирован, а не «быстрый map с mutex» по умолчанию.
Ключевые понятия
Элемент map не адресуем: runtime может переместить его при росте, поэтому нельзя написать m[k].Field = x. Достаньте значение, измените и запишите обратно.
type Counter struct{ N int }
m := map[string]Counter{"ok": {N: 1}}
c := m["ok"]
c.N++
m["ok"] = c
if v, ok := m["missing"]; !ok { fmt.Println("нет ключа") } else { fmt.Println(v) }Для обычного map критическая секция охватывает все обращения:
type Cache struct {
mu sync.RWMutex
m map[string]string
}
func (c *Cache) Get(k string) (string, bool) { c.mu.RLock(); defer c.mu.RUnlock(); v, ok := c.m[k]; return v, ok }
func (c *Cache) Put(k, v string) { c.mu.Lock(); defer c.mu.Unlock(); c.m[k] = v }range намеренно имеет нефиксированный порядок: для детерминированного вывода соберите и отсортируйте ключи.
Типовые вопросы
- Почему
m[k].Field = xне компилируется?- Значение map не addressable; измените локальную копию и присвойте её обратно.
- Можно ли читать map параллельно?
- Несколько чтений без записей допустимы; любой конкурентный writer требует синхронизации всех обращений.
- Гарантирован ли порядок
range?- Нет; он не предназначен для стабильного порядка.
- Что означает нулевое значение при lookup?
- Оно не отличает отсутствующий ключ от ключа со значением zero value; нужен
ok.
- Оно не отличает отсутствующий ключ от ключа со значением zero value; нужен
- Когда выбрать
sync.Map?- Для его документированных паттернов: write-once/read-many или разрозненные ключи; иначе часто проще map с mutex.
Практика
- Реализуйте счётчик по ключу с
Mutex. Готово:go test -raceне находит race, а API не отдаёт внутренний map. - Верните ключи map в лексикографическом порядке. Готово: тест не зависит от порядка
range.
Частые ошибки и ловушки
- Использовать nil map для записи: это panic; инициализируйте
make. - Защищать только записи, оставляя чтения без того же lock.
- Держать lock во время медленного I/O или callback.
Связанные темы
Коллекции и работа с данными · Конкурентность