Зачем нужны указатели в структуре с мапой и мьютексом в Go, если мапа является ссылочным типом
Что проверяют
Понимаете ли вы, что копируется при передаче структуры по значению: мапу копировать безопасно, а мьютекс нельзя. Следом обычно просят найти ошибку в методе с получателем по значению или объяснить, что скажет go vet.
Как отвечать
Начните с того, что мапа сама по себе не проблема. Значение типа map это указатель на внутреннюю структуру рантайма, поэтому копия структуры видит те же данные, а копирование дешёвое. Проблема в соседнем поле:
sync.Mutexнельзя копировать после первого использования: копия структуры получает свой мьютекс, и две копии охраняют одну мапу разными замками. Итог: гонка данных иfatal error: concurrent map writes, которыйrecoverне перехватывает;- копия запертого мьютекса: если скопировать структуру, пока замок взят, копия родится запертой, и первый
Lockна ней повиснет навсегда; - получатель по значению: метод
func (s SafeMap) Set(...)получает копию при каждом вызове, иs.mu.Lock()запирает копию, не защищая ничего. Поэтому все методы делают с получателем*SafeMap, а структуру отдают черезNewSafeMap() *SafeMap; - нулевое значение: без конструктора поле
dataравноnil, чтение работает, запись паникует. Нужен конструктор или ленивая инициализация под замком.
Ловится это инструментами: go vet с проверкой copylocks предупреждает о копировании значения с мьютексом, а go test -race показывает гонку при выполнении.
Про альтернативы: sync.RWMutex помогает при большом перевесе чтений, а sync.Map рассчитан на узкие сценарии, когда ключ пишется один раз и много читается или горутины работают с непересекающимися ключами.
Где спотыкаются
Объясняют указатель экономией памяти: «мапа большая, её дорого копировать». Мапа копируется как один указатель, дело в мьютексе.
Встраивают sync.Mutex анонимно и выставляют Lock и Unlock наружу как часть публичного API типа.
Думают, что при копировании структуры изменения мапы теряются. Наоборот, обе копии пишут в одну мапу, только без общей защиты.