Как случайно столкнуться с гонкой данных при работе с Go
Краткое резюме
В статье «Как наткнуться на Data Race в Go» Филипп Готье описывает проблемы гонки данных в языке программирования Go и предлагает способы их решения. Data Race возникает, когда несколько потоков обращаются к одному участку памяти без синхронизации, что может привести к повреждению памяти.
Филипп Готье, автор блога, опубликовал перевод статьи «Миллион способов погибнуть из-за гонки данных в Go». Материал предназначен для разработчиков, имеющих опыт работы с этим языком программирования.
Автор делится своими наблюдениями о создании приложений на Go, отмечая как положительные, так и отрицательные аспекты. Одним из проблемных моментов он называет лёгкость, с которой можно столкнуться с непреднамеренной гонкой данных.
Гонка данных (Data Race) — это ситуация, когда несколько конкурентных потоков или горутин обращаются к одному участку памяти без синхронизации, причём хотя бы один из них осуществляет запись.
Хотя Go часто хвалят за простоту и элегантность организации конкурентности, при недостаточной внимательности даже в таком языке можно совершить множество ошибок.
За годы работы автор столкнулся с различными проявлениями гонки данных в Go и нашёл способы их устранения. Он также упоминает, что ранее исследовал проблемы конкурентности в Go, не всегда связанные с гонкой данных.
В контексте языка Go гонка данных — это код, который не соответствует модели памяти Go (The Go Memory Model). Эта модель определяет, как компилятор должен и может обрабатывать код, провоцирующий состояние гонки данных. Гонки данных в Go могут привести к непреднамеренному повреждению памяти, особенно в составных структурах данных, где важна согласованность внутренних компонентов (например, pointer и type, pointer и length). К таким структурам относятся интерфейсы, мапы, слайсы, пользовательские структуры и строки.
Далее автор переходит к рассмотрению реальных случаев гонки данных в коде Go и предлагает решения для них. В конце он даёт несколько рекомендаций по предотвращению подобных ситуаций.
Также рекомендуется ознакомиться со статьёй «A Study of Real-World Data Races in Golang», которая, как надеется автор, станет духовным дополнением к его материалу. Некоторые из представленных пунктов уже присутствуют в этой статье, а некоторые являются новыми.
В своих примерах автор часто использует errgroup.WaitGroup и sync.WaitGroup, поскольку они предоставляют удобный API для работы с моделью fork-join, сокращая объём шаблонного кода. Однако он подчёркивает, что использование высокоуровневых абстракций не гарантирует защиты от состояния гонки данных.