Как решать гонку данных
Что проверяют
Отличаете ли вы гонку данных от логической гонки и знаете ли, чем Go ловит обе. Следом почти всегда спрашивают, когда брать мьютекс, а когда канал, и что на самом деле гарантирует флаг -race.
Как отвечать
Начните с точного определения: гонка данных это одновременный доступ двух горутин к одной памяти, где хотя бы один доступ пишет, а между ними нет синхронизации, то есть отношения happens-before из модели памяти Go. Поведение такой программы не определено, и «обычно работает» ничего не значит.
Инструменты по задаче:
sync.Mutexиsync.RWMutex: защищают составное состояние, например структуру с несколькими полями.Unlockставят черезdefer, чтобы паника не оставила мьютекс захваченным;sync/atomic: для одиночного счётчика или флага. С Go 1.19 есть типыatomic.Int64,atomic.Bool,atomic.Pointer[T], с ними сложнее случайно прочитать значение мимо атомика;- каналы: когда данные передаются во владение другой горутине. Принцип «не общайтесь через разделяемую память, делитесь памятью через общение»;
sync.Once,sync.WaitGroup,errgroup: разовая инициализация и ожидание группы горутин.
Для поиска гонок есть детектор: go test -race, go run -race, go build -race. Он ловит только гонки, которые реально случились в прогоне, и заметно замедляет программу, поэтому его гоняют в CI на тестах, а не в проде. Отдельно упомяните обычную map: конкурентная запись в неё роняет программу фатальной ошибкой concurrent map writes, которую не перехватить через recover.
Где спотыкаются
Называют несуществующую команду go race или считают, что зелёный прогон с -race доказывает отсутствие гонок. Детектор видит только исполненные пути.
Путают гонку данных с race condition. Можно сделать каждую операцию атомарной и всё равно получить ошибку в логике «проверил, потом записал», если проверка и запись не под одной блокировкой.
Копируют структуру с мьютексом по значению, например передают её в функцию без указателя. Копия блокирует уже другой мьютекс, и защиты нет; go vet с проверкой copylocks такое находит.