В чем разница map и sync.Map в Go и когда использовать каждый из них
Что проверяют
Знаете ли вы, что sync.Map оптимизирована под узкий сценарий, и понимаете ли, почему обычная карта с мьютексом чаще лучше.
Как отвечать
Обычная map не потокобезопасна: параллельная запись обнаруживается рантаймом и роняет программу с явным сообщением. Для конкурентного доступа её оборачивают в sync.Mutex или sync.RWMutex.
sync.Map это готовая потокобезопасная карта, но не универсальная замена. Внутри у неё два хранилища: почти неизменяемое для чтения без блокировок и грязное с блокировкой для записей. Такая конструкция окупается в двух сценариях, которые прямо названы в документации:
- ключ пишется один раз, а читается много (кэш, который только пополняется);
- разные горутины работают с непересекающимися наборами ключей.
Во всех остальных случаях, особенно при частых записях в одни и те же ключи, обычная карта под RWMutex быстрее: у sync.Map есть накладные расходы на копирование грязной карты и на атомарные операции.
Практические минусы sync.Map: нет типизации (ключи и значения это any, поэтому приведение типов и упаковка), нет len, обход только через Range без гарантии согласованного снимка.
Правило выбора: по умолчанию обычная карта с мьютексом, потому что она типизирована и предсказуема. sync.Map берут после замеров и только под её сценарий. С появлением обобщений часто пишут свою типизированную обёртку с мьютексом, и это лучший вариант по читаемости.
Где спотыкаются
Считают sync.Map универсально более быстрой.
Не знают про рантайм-детектор параллельной записи и ищут причину падения в своём коде.
Ждут от sync.Map метода получения размера.
Оборачивают в мьютекс каждое обращение, включая чтение, там где RWMutex дал бы выигрыш.