Зачем нужен sync.Map в Go если есть map с мьютексом
Что проверяют
Знаете ли вы, что sync.Map не универсальная замена map с мьютексом, а специализированная структура под два конкретных сценария. Сильный ответ опирается на документацию: в большинстве случаев нужен обычный map с отдельной блокировкой. Следом спрашивают, как бы вы доказали выигрыш бенчмарком.
Как отвечать
Сначала база: обычный map в Go не потокобезопасен. Одновременная запись из нескольких горутин заканчивается не паникой, которую ловит recover, а фатальной ошибкой concurrent map writes, и процесс падает. Поэтому map закрывают sync.Mutex, а если чтений намного больше, то sync.RWMutex.
Документация называет два случая, где sync.Map выигрывает за счёт меньшей конкуренции за блокировку:
- ключ записывается один раз и потом много раз читается: кэши, которые только растут;
- горутины работают с непересекающимися наборами ключей: каждая пишет и перезаписывает свои записи.
В остальных сценариях, особенно при частой записи в общие ключи, map с мьютексом обычно не хуже, а код проще.
Цена sync.Map:
- нет типизации: ключи и значения типа
any, на каждом чтении приведение типа. Поэтому часто пишут дженерик-обёртку; - нет
Len: размер считается только черезRange; Rangeне снимок: при параллельных изменениях обход может увидеть часть новых данных;- копировать нельзя после первого использования, как и мьютекс.
Полезны атомарные методы, которые с мьютексом пришлось бы писать руками: LoadOrStore, LoadAndDelete, Swap, CompareAndSwap, а с Go 1.23 ещё и Clear. Внутреннее устройство менялось: раньше это была пара из read-only карты и dirty-карты под мьютексом, а в Go 1.24 реализацию перевели на конкурентное хэш-дерево, что ускорило в первую очередь запись.
Где спотыкаются
Говорят, что sync.Map работает без блокировок. Внутри есть и мьютексы, и атомики, просто при частых чтениях до блокировки дело доходит редко.
Обещают, что sync.Map всегда быстрее. Без бенчмарка на своём профиле нагрузки это утверждение ничего не стоит.
Пишут «если ключа нет, положить» двумя вызовами, Load и затем Store. Между ними вклинится другая горутина, для этого и существует LoadOrStore.