#20.? golang go tun -race Обнаружение состояние гонки средствами компилятора - WARNING: DATA RACE

Обнаружение гонки данных

Чтобы обнаружить гонку данных средствами golang-а, можно запустить программу с флагом -race, например:

go run -race test.go 

где test.go - имя файла с исходным кодом

Например, если мы запустим такой код:

package main

import (
	"fmt"
	"time"
)

func incIt(a *int) {
	*a += 1 // инкрементируем
	fmt.Print(*a, "_")
}

func main() {
	a := 1
	go incIt(&a)
	go incIt(&a)

	// Ждем завершения горутин
	time.Sleep(500 * time.Millisecond) // полсекунды должно хватить

}

то получим ответ вида:

2_==================
WARNING: DATA RACE
Read at 0x00c00011a028 by goroutine 9:
  main.incIt()
      /var/www/go-test/test.go:9 +0x30
  main.main.gowrap2()
      /var/www/go-test/test.go:21 +0x33

Previous write at 0x00c00011a028 by goroutine 8:
  main.incIt()
      /var/www/go-test/test.go:9 +0x44
  main.main.gowrap1()
      /var/www/go-test/test.go:20 +0x33

Goroutine 9 (running) created at:
  main.main()
      /var/www/go-test/test.go:21 +0x10a

Goroutine 8 (running) created at:
  main.main()
      /var/www/go-test/test.go:20 +0xa4
==================
3_Found 1 data race(s)
exit status 66

-- WARNING: DATA RACE тут как раз и обозначает, что состояние гонки зафисировано

Как компилятор обнаруживает состояние гонки

1. Этап компиляции: Инструментирование

Когда мы добавляем флаг -race, компилятор Go переходит в специальный режим. Вместо обычного машинного кода он генерирует «инструментированный» код, где:

  1. Перехват обращений к памяти: Каждое чтение или запись переменной в коде дополняется вызовом функции из библиотеки ThreadSanitizer (например, __tsan_read или __tsan_write).
  2. Регистрация событий: Компилятор также вставляет вызовы при входе и выходе из функций, а также при операциях синхронизации (захват мьютекса, отправка в канал - если такие операции вообще есть).

2. Этап выполнения: Алгоритм Vector Clocks

Когда программа запущена, детектор следит за всеми обращениями к памяти и использует логические часы (Vector Clocks) для отслеживания цепочки событий.
Детектор хранит для каждого адреса памяти (размером в 8 байт) небольшую «теневую» запись (shadow memory), где записано:

  1. Кто последний обращался к этой памяти (ID горутины).
  2. Когда это было (время по логическим часам).
  3. Тип операции (чтение или запись).

Как принимается решение о гонке:

Пример ситуации:

  1. Горутина №2 хочет записать данные по адресу X.
  2. Детектор проверяет теневую память этого адреса и видит, что последней там была Горутина №1 (делала чтение или запись).
  3. Детектор ищет связь: «Было ли событие синхронизации (мьютекс или канал) между Горутиной 1 и Горутиной 2?».
  4. Если связи нет, и одна из операций — запись, то это Data Race. Программа немедленно печатает отчет.

-- для последнего примера код выша получим вывод вида:

Key Words for FKN + antitotal forum (CS VSU):