Почему мапа в Go имеет встроенный race detector, а другие типы нет
Что проверяют
Знаете ли вы, что в рантайме Go встроена не защита мапы, а аварийная проверка, и понимаете ли разницу между ней и детектором гонок из сборки с -race. Заодно смотрят, не считаете ли вы встроенную мапу потокобезопасной.
Как отвечать
Начать стоит с поправки к самому вопросу. Встроенного детектора гонок у мапы нет. В её заголовке есть флаг записи, и если во время записи приходит другая запись или чтение, рантайм останавливает программу:
fatal error: concurrent map writes
fatal error: concurrent map read and map writeЭто fatal error, а не паника: recover() её не перехватывает, процесс падает целиком. Проверка стоит один бит и одно ветвление, срабатывает не в каждом случае и гарантией не является.
Почему именно мапа. Запись может вызвать рост таблицы и переезд бакетов, и чужая запись в этот момент портит внутренние структуры: дальше рантайм идёт по битым указателям, а это повреждение памяти, которое всплывёт в случайном месте через часы. Дешевле уронить процесс сразу. У слайса или числа гонка даёт неверное значение или порванный заголовок, инварианты аллокатора при этом целы, а проверка на каждой записи стоила бы дороже самой записи.
Детектор гонок это отдельный инструмент: сборка с флагом -race, работает по любым типам, замедляет программу в разы и заметно ест память, поэтому живёт в тестах и на стендах, а не в проде.
Дальше называем обычные способы защиты: sync.RWMutex рядом с мапой, sync.Map для сценария «много чтений, ключи пишутся редко», шардирование по хешу ключа, либо владение мапой одной горутиной и общение через канал.
Где спотыкаются
Читают сообщение рантайма как «мапа синхронизируется сама». Наоборот: увидеть его можно только на уже упавшем процессе, и больше рантайм не делает ничего.
Пытаются прикрыть доступ через defer recover(). Фатальные ошибки рантайма не перехватываются, и такой код даёт лишь ложное чувство надёжности.
Считают опасными одновременные чтения. Читать мапу из нескольких горутин можно сколько угодно, пока никто не пишет; роняет программу именно запись параллельно с чтением.