Являются ли мапы в Go потокобезопасными
Что проверяют
Знаете ли вы, что происходит с обычной map при конкурентном доступе, и умеете ли выбрать способ защиты под конкретную нагрузку. Следом почти всегда спрашивают, когда sync.Map лучше мапы с мьютексом, а когда хуже.
Как отвечать
Нет, встроенная map не потокобезопасна. Одновременное чтение из нескольких горутин допустимо, но если хотя бы одна горутина пишет параллельно с другими, это гонка данных. Рантайм ловит такие ситуации на лету и завершает программу фатальной ошибкой concurrent map writes или concurrent map read and map write. Это не обычная паника: recover её не перехватит, падает весь процесс. Проверка рантайма срабатывает не всегда, надёжно гонки ловит только детектор: go test -race.
Способы защиты:
sync.RWMutexрядом с мапой: вариант по умолчанию.RLockдля чтения,Lockдля записи, мапа и мьютекс лежат в одной структуре, доступ только через её методы;sync.Map: по документации оптимизирована под два сценария: ключ пишется один раз и много раз читается, либо горутины работают с непересекающимися наборами ключей. Значения хранятся какany, при чтении нужна проверка типа;- шардирование: мапа делится на N частей со своим мьютексом у каждой, шард выбирается по хэшу ключа. Помогает, когда один мьютекс стал точкой конкуренции;
- владение одной горутиной: мапу трогает только она, остальные шлют запросы через канал.
Хорошо добавить, что на смешанной нагрузке с частыми записями sync.Map нередко проигрывает обычной мапе с мьютексом, и выбор стоит подтверждать бенчмарком.
Где спотыкаются
Считают, что чтение безопасно всегда. Безопасно только чтение без параллельной записи, одна пишущая горутина делает гонкой и все чтения.
Берут RLock в методе, который меняет мапу, или копируют структуру с мьютексом по значению. Второе ловит go vet проверкой copylocks, но только если его запускают.
Советуют sync.Map как универсальную замену. Без типизации, без len и с худшей скоростью на частых записях она оправдана только в своих сценариях.