Даже со встроенной в Go поддержкой параллелизма ошибки все равно случаются. Легко случайно сослаться на переменную из двух разных горутин без получения блокировки. В компьютерных науках для этого используется термин гонка данных. Чтобы помочь найти такие ошибки, в Go есть проверка расы. Нет гарантии, что в вашем коде будет найдена каждая гонка данных, но если она найдется, вы должны установить соответствующие блокировки вокруг того, что она находит.

Давайте рассмотрим простой пример:

func getCounter() int { 
    var counter int
    var wg sync.WaitGroup 
    wg.Add(5) 
    for i:=0;i<5;i++{
      go func() { 
          fori:=0;i<1000;i++{
              counter++ 
           }
                      wg.Done()
                  }()
    }
    wg.Wait() 
    return counter
}

Этот код запускает пять горутин, каждая из которых обновляет общую переменную счетчика 1000 раз, а затем возвращает результат. Вы ожидаете, что это будет 5000, поэтому давайте проверим это с помощью модульного теста:

func TestGetCounter(t *testing.T) { 
    counter := getCounter()
    if counter != 5000 {
                t.Error("unexpected counter:", counter)
            }
}

Если вы запустите go test несколько раз, вы увидите, что иногда он проходит успешно, но в большинстве случаев происходит сбой с сообщением об ошибке, например:

unexpected counter: 3673

Проблема в том, что в коде происходит гонка данных. В такой простой программе причина очевидна: несколько горутин пытаются обновить счетчик одновременно, и некоторые из их обновлений теряются. В более сложных программах такие гонки труднее увидеть. Давайте посмотрим, что делает средство проверки расы. Используйте флаг -race с go test, чтобы включить его:

$ go test -race
    ==================
    WARNING: DATA RACE
    Read at 0x00c000128070 by goroutine 10:
      test_examples/race.getCounter.func1()
                test_examples/race/race.go:12 +0x45
    Previous write at 0x00c000128070 by goroutine 8:
          test_examples/race.getCounter.func1()
            test_examples/race/race.go:12 +0x5b

Следы дают понять, что строка counter++ является источником наших проблем.

Примечание:

Некоторые люди пытаются исправить условия гонки, вставляя в свой код «слипы», пытаясь разграничить доступ к переменной, к которой обращаются несколько горутин. Это плохая идея. Это может устранить проблему в некоторых случаях, но код по-прежнему неверен, и в некоторых ситуациях он не работает.

Подводя итог, вы также можете использовать флаг -race при сборке своих программ. Это создает двоичный файл, который включает средство проверки гонок и сообщает о любых гонках, которые он находит, на консоль. Это позволяет вам находить гонки данных в коде, в котором нет тестов.

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