Data race и владелец-синхронизатор
После разбора команда договорилась: у каждой общей переменной должен быть хозяин, который синхронизирует доступ. Первым вопросом на ревью стало: кто владеет счётчиком. Ответ «никто» Аня закрывала как баг, даже если код работал.
data race — это два обращения к одной ячейке без happens-before, и хотя бы одно из них запись; у любой общей переменной должен быть синхронизирующий владелец.
Определение гонки
Что здесь показано
- Стек — локальные переменные функции.
i = 3— значение;p = ptr → 10— указатель на значение;b = "hello"— срез (заголовок: указатель + длина + ёмкость). - Куча — данные, на которые ссылаются переменные:
rodata— байты строки-литерала (в бинарнике),данные N Б— бэкинг-массив слайса,строка— динамическая строка. - Горутины — лёгкие потоки Go: статус (running/runnable/waiting…), функция, в которой каждая сейчас находится, размер стека (растёт по мере работы) и пометка «из main» — горутина запущена из твоего кода.
- GC — сборщик мусора: фаза (off/mark) и число завершённых циклов.
Две операции обращаются к одной ячейке памяти, хотя бы одна из них запись, и между ними нет happens-before. Такая программа некорректна, но исходы ограничены моделью памяти: для однословной ячейки читатель видит реально записанное старое или новое значение — out-of-thin-air значения запрещены. Смесь значений из разных записей возможна только для многословных объектов — структур, интерфейсов, слайсов, карт, строк. В примере int — одно слово, поэтому читатель увидит либо старое, либо новое значение.
Владелец-синхронизатор
Практический вывод из модели памяти: у любой переменной, к которой обращаются несколько горутин, обязан быть синхронизирующий владелец.
- Данные ходят через канал: владелец — канал.
- Доступ под мьютексом: владелец — мьютекс.
- Значение обновляется атомарно: владелец — атомик.
Всё остальное — гонка, даже если на конкретной машине и конкретной версии Go «работает». Вопрос «кто владеет этой переменной» стоит задавать про каждую общую переменную при ревью.
«просто прочитать и так сойдёт» не работает
Если переменную читают из нескольких горутин, даже «просто чтение» требует синхронизации — потому что писатель может менять её в этот самый момент. Без синхронизации читатель не обязан видеть ничего: компилятор вправе держать значение в регистре, процессор — переупорядочить обращения.
Вы узнаете:
- почему гонка может не проявляться годами
- как гонка ловится
-race
Проверьте себя
Код с гонкой стабильно работает неделю. Что это доказывает?
Одна горутина читает counter, другая делает counter++. Что скажет -race?
При гонке на однословной ячейке что видит читатель?
правильных ответов
Гонка = два обращения без happens-before, одно из них запись. Лекарство — синхронизирующий владелец у каждой общей переменной: канал, мьютекс или атомик.
10 000 ₽ · 60 минут · онлайн