diff --git a/concurrency/race_condition.md b/concurrency/race_condition.md index fea040a..5712db2 100644 --- a/concurrency/race_condition.md +++ b/concurrency/race_condition.md @@ -1,4 +1,4 @@ -# Многопоточность. Race condition +# Многопоточность. Data race Разработка многопоточных приложений это всегда сложно. Проблема синхронизации доступа к разделяемым данным — вечная головная боль. Хорошо, если у вас уже есть оттестированная и проверенная временем библиотека контейнеров, высокоуровневых примитивов, параллельных алгоритмов, берущих на себя контроль за всеми инвариантами. Очень хорошо, если статические проверки компилятора не позволят вам использовать все это добро неправильно. Ах, как было бы хорошо... @@ -9,7 +9,7 @@ Увы, так не работает. Правильность использования этих инструментов в C++ нужно контроллировать самостоятельно, пристально изучая каждую строчку кода. Мы все равно втыкаемся в проблемы синхронизации доступа, с аккуратным развешиванием мьютексов и атомарных переменных. -Ситуация (_race condition_), в которой один поток программы модифицирует объект, а другой, в то же самое время, читает значения из этого объекта — или просто два потока одновременно пытаются модифицировать один объект — совершенно ясно, является ошибочной. Результат чтения может выдать какой-то странный промежуточный объект. Совместная запись — породить какое-то мутировавшее премешаное значение. Независимо от языка программирования. +Ситуация (_data race_), в которой один поток программы модифицирует объект, а другой, в то же самое время, читает значения из этого объекта — или просто два потока одновременно пытаются модифицировать один объект — совершенно ясно, является ошибочной. Результат чтения может выдать какой-то странный промежуточный объект. Совместная запись — породить какое-то мутировавшее премешаное значение. Независимо от языка программирования. Но в C++ это не просто ошибка. Это неопределенное поведение. И «возможности» для оптимизации @@ -19,7 +19,7 @@ int func(const std::vector& v) { for (size_t i = 0; i < v.size(); ++i) { sum += v[i]; } - // Race condition запрещен, от модификации v в + // Data race запрещен, от модификации v в // параллельном потоке нас «защищает» UB. // А значит можно соптимизировать вычисление size @@ -61,11 +61,11 @@ int main() { Но [если включить оптимизации](https://godbolt.org/z/PoqbMb), цикл в первом потоке либо не выполнит ни одной итерации (clang), либо никогда не завершится (gcc)! -Оба компилятора видят, что доступ не синхронизирован. Race condition запрещен. Значит, и синхронизировать не надо. Значит, при обращении к переменной `terminate` в заголовке цикла +Оба компилятора видят, что доступ не синхронизирован. Data race запрещен. Значит, и синхронизировать не надо. Значит, при обращении к переменной `terminate` в заголовке цикла всегда должно быть одно и то же значение. gcc решает, что всегда будет `false`. clang обнаруживает присваивание `terminated = true` в другом потоке и вытягивает его перед началом цикла. Конечно же, тут ошибка намеренная и исправляется легко заменой `bool` на `std::atomic`. -Но в реальной кодовой базе допустить race condition просто, а исправить сложнее. +Но в реальной кодовой базе допустить data race просто, а исправить сложнее. Однажды я написал что-то подобное: @@ -118,7 +118,7 @@ std::jthread t2 { [&] { В поиске проблем с доступом к объектам из разных потоков вам помогут статические анализаторы и санитайзеры: например, tsan для gcc/clang (`-fsanitize=thread`). Насколько мне известно, текущая реализация tsan (2021 год) не дружит с asan (address sanitized). Так что не выйдет махом искать и race сondition, и обычные ошибки доступа к памяти с нарушением lifetime. -В Rust race condition отлавливается статически компилятором. Однако, неаккуратно используя `unsafe`, и в нем можно устроить себе проблемы. И будет неопределенное поведение. На то оно и `unsafe`. +В Rust нельзя создать data race и вызвать неопределенное поведение в безопасном подмножестве языка. Однако, неаккуратно используя `unsafe`, и в нем можно устроить себе проблемы. И будет неопределенное поведение. На то оно и `unsafe`. # Полезные ссылки