Go race detector: как найти гонку данных и проверить исправление

Go race detector: как найти гонку данных и проверить исправление
Содержание статьи

Запустите тесты Go с флагом -race, если несколько горутин обращаются к общим данным и хотя бы одна их меняет. Детектор помогает найти несинхронизированные конфликтующие обращения, но видит только выполненные пути. Успешный запуск без предупреждений не доказывает, что в программе нет гонок.

Как запустить проверку

В каталоге учебного модуля с существующими тестами используйте команду:

go test -race ./...

Флаг требует поддерживаемой платформы, включённого cgo и подходящего C-компилятора. Если инструмент не запускается из-за окружения, это ещё не результат проверки кода. Не отключайте проблемный тест, чтобы получить зелёный вывод: сначала отделите ошибку запуска от найденной гонки.

В отчёте WARNING: DATA RACE найдите стеки двух конфликтующих обращений и места создания горутин. Сопоставьте их с одним общим объектом. Сообщение о записи не означает, что достаточно защитить только записывающую сторону: все конкурентные обращения должны следовать согласованному правилу синхронизации.

Почему WaitGroup недостаточно

Операция n++ включает чтение и запись. Две горутины, выполняющие её для одной переменной без защиты, создают гонку. WaitGroup позволяет дождаться завершения работ, но не даёт им исключительного доступа к счётчику.

В следующем небольшом тесте доступ к счётчику защищён одним общим Mutex. Сохраните его как counter_test.go в учебном модуле с соответствующим именем пакета.

package counter

import (
    "sync"
    "testing"
)

func TestCounter(t *testing.T) {
    var mu sync.Mutex
    var wg sync.WaitGroup
    n := 0
    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for j := 0; j < 100; j++ {
                mu.Lock()
                n++
                mu.Unlock()
            }
        }()
    }
    wg.Wait()
    if n != 200 {
        t.Fatalf("got %d, want 200", n)
    }
}

Все изменения n проходят под одной блокировкой. Итог читается после Wait(), когда работники завершены. Здесь важно не только число 200: без синхронизации оно иногда могло бы случайно получиться правильным. Mutex нельзя копировать после начала использования.

Как проверить исправление осмысленно

Сохраните сценарий, который вызывает конкурентные обращения, и повторите его с детектором после изменения. Проверьте также обычный функциональный результат. Если тест вообще не запускает спорный путь, отсутствие предупреждения почти ничего не говорит об исправлении этого пути.

Race detector не является универсальным поиском дедлоков, утечек горутин и логических ошибок порядка операций. Для них нужны свои сценарии и наблюдения. Блокировка должна защищать общий инвариант, а не просто скрывать отдельную строку из отчёта.

Изучить модель конкурентности можно на курсе Go. Для разбора отчёта детектора на Mentoring.digital есть наставники по Go: принесите минимальный воспроизводимый пример и команду запуска.

Похожие статьи по данной теме