Можно ли использовать map одновременно из нескольких горутин без синхронизации
Что проверяют
Знаете ли вы, что встроенный map в Go не потокобезопасен и что рантайм реагирует на гонку не тихой порчей данных, а аварийной остановкой. Следом обычно спрашивают, чем sync.Map отличается от map под sync.RWMutex и когда какой выбрать.
Как отвечать
Короткий ответ: одновременное чтение из нескольких горутин безопасно, если никто не пишет. Как только хотя бы одна горутина пишет, нужна синхронизация. Рантайм отслеживает флаг записи и при обнаружении падает с fatal error: concurrent map writes или concurrent map read and map write. Это не паника: recover её не перехватывает, процесс завершается целиком. Проверка при этом выборочная, поэтому гонку надёжно ловит только детектор: go test -race или go run -race.
Варианты защиты:
sync.Mutex: самый простой и предсказуемый вариант, map и мьютекс кладут в одну структуру с методамиGet/Set;sync.RWMutex:RLockдля чтения,Lockдля записи. Выигрывает, когда чтений заметно больше, чем записей;sync.Map: по документации рассчитан на два сценария: ключ записали один раз и потом много читают, либо разные горутины работают с непересекающимися наборами ключей. Методы принимаютany, типобезопасности нет,lenтоже нет;- шардирование: N карт, у каждой свой мьютекс, шард выбирается по хэшу ключа. Снимает конкуренцию за одну блокировку под высокой нагрузкой;
- горутина-владелец: map живёт в одной горутине, остальные шлют ей запросы через канал. Уместно, когда вокруг map есть ещё логика.
Где спотыкаются
Надеются на recover. Ошибка конкурентного доступа к map фатальная, сервис падает без шанса её обработать, поэтому «обернём в defer» не спасает.
Защищают мьютексом только запись, а читают без блокировки. Чтение параллельно с записью такая же гонка, и детектор её покажет.
Передают структуру с мьютексом по значению. Копия получает собственный мьютекс, и защита исчезает. Это ловит go vet (проверка copylocks), а методы такой структуры стоит объявлять на указателе.