GoMiddleCoding
Что такое race detector в Go и как его использовать?
Race detector включается флагом -race и использует ThreadSanitizer для обнаружения одновременных незащищённых обращений к памяти в рантайме. Применяется в тестах и CI, в продакшн бинарь не включают из-за накладных расходов.
Race detector в Go
Race detector — встроенный инструмент обнаружения гонок данных (data races). Он основан на алгоритме ThreadSanitizer (TSan) и отслеживает все обращения к памяти во время выполнения программы. Если два горутина одновременно читают и пишут одну переменную без синхронизации, детектор немедленно сообщает о нарушении.
Как включить
Флаг -race можно добавить к любой стандартной команде:
go run -race main.go # запустить с детектором
go test -race ./... # тесты всех пакетов
go build -race -o myapp . # собрать бинарь с детектором
go test -race -count=5 ./... # несколько прогонов для воспроизведения
Пример гонки и вывода
package main
import (
"fmt"
"sync"
)
func main() {
counter := 0
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter++ // гонка данных!
}()
}
wg.Wait()
fmt.Println(counter)
}
Запуск go run -race main.go выдаст примерно:
==================
WARNING: DATA RACE
Write at 0x00c0000b4010 by goroutine 7:
main.main.func1()
/tmp/main.go:15 +0x44
Previous write at 0x00c0000b4010 by goroutine 6:
main.main.func1()
/tmp/main.go:15 +0x44
==================
Исправление
var mu sync.Mutex
go func() {
defer wg.Done()
mu.Lock()
counter++
mu.Unlock()
}()
// или использовать atomic.AddInt64
Накладные расходы
- Память: ~5-10x
- Время выполнения: ~2-20x
- Размер бинаря: увеличивается
Поэтому race detector включают только в тестах и staging, но не в продуктивных бинарях.
Переменные окружения
GORACE="log_path=/tmp/race.log halt_on_error=1" go test -race ./...
# log_path — куда писать отчёты
# halt_on_error=1 — остановить при первой гонке
# strip_path_prefix — обрезать префикс в путях
Подводные камни
- Race detector обнаруживает только фактические гонки в момент выполнения — гонки в неисполненных ветках кода пропускаются.
- Детектор не работает на
unsafe-операциях — они обходят всю инструментацию. - Включение
-raceв продуктивный бинарь резко увеличивает потребление памяти; под нагрузкой это вызывает OOM. - Ложноотрицательные результаты возможны при слабом тестовом покрытии: гонка есть, но ни один тест её не воспроизводит.
- Детектор требует cgo для TSan, поэтому он недоступен при полной статической сборке (
CGO_ENABLED=0). - На macOS ARM64 (Apple Silicon) ранние версии Go давали некорректные трассировки; важно использовать Go 1.19+.
- В CI рекомендуется запускать
go test -race -count=3 ./...— некоторые гонки проявляются только при повторных запусках.
Common mistakes
- Давать ответ про race detector только на уровне определения, не показывая поведение в реальном приложении.
- Игнорировать границы ответственности вокруг темы «race detector»: кто отменяет работу, кто владеет ресурсом и где формируется ответ клиенту.
- Не связывать race detector с observability, тестированием или безопасностью, когда это влияет на продакшен-поведение.
What the interviewer is testing
- Точно объясняет, что именно делает race detector и где это используется в Go-коде.
- Связывает race detector с корректным lifecycle запроса, отменой, конкурентностью или конфигурацией сервера там, где это уместно.
- Не изобретает API и опирается на реальные контракты официальной документации.