Опасности одновременной записи и чтения логического значения в простой ситуации

Я читал несколько похожих вопросов, но описанные там ситуации немного сложнее.

у меня есть bool b инициализируется как false в кучу и две темы. Я понимаю, что операции с bools являются not atomic, но, пожалуйста, прочитайте вопрос до конца.

Первый поток может установить b = true только один раз и больше ничего с этим не делает. Проверка второго потока b в цикле, и если это true делает некоторые действия.

Нужно ли использовать какой-то механизм синхронизации (например, мьютексы) для защиты b? Что может случиться, если я этого не сделаю? С ints Очевидно, что я могу получить произвольные значения, когда читаю и пишу одновременно. Но с bools есть только true а также false и я не против когда-то получить false вместо true, Это потенциал SIGSEGV?

5 ответов

Решение

Гонки данных приводят к неопределенному поведению. Что касается стандарта, то соответствующая реализация допускается к segfault.

На практике главная опасность состоит в том, что без синхронизации компилятор будет наблюдать достаточно кода в цикле чтения, чтобы судить, что b "никогда не меняется" и оптимизировать все, кроме первого чтения значения. Он может сделать это, потому что, если он наблюдает, что в цикле нет синхронизации, то он знает, что любая запись в значение будет гонкой данных. Оптимизатору разрешается предполагать, что ваша программа не вызывает неопределенного поведения, поэтому разрешается предполагать, что нет записей из других потоков.

маркировка b как volatile предотвратит эту конкретную оптимизацию на практике, но даже на volatile Гонки данных объектов имеют неопределенное поведение. Обращение к коду, который оптимизатор "не видит", также на практике предотвратит оптимизацию, поскольку он не знает, изменяет ли этот код b, Конечно, с оптимизацией по времени соединения / всей программе оптимизатор не может видеть меньше, чем с оптимизацией только во время компиляции.

В любом случае, предотвращение оптимизации в программном обеспечении не препятствует тому, чтобы подобное происходило на оборудовании в системе с некогерентными кэшами (по крайней мере, поэтому я утверждаю: другие люди утверждают, что это не правильно, и что volatile Доступ требуется для чтения / записи через кэши. Некоторые реализации ведут себя именно так). Если вы спрашиваете о том, что говорит стандарт, тогда на самом деле не имеет значения, показывает ли аппаратное обеспечение устаревший кэш неопределенно долго, так как поведение остается неопределенным, и поэтому реализация может нарушить ваш код независимо от того, является ли эта конкретная оптимизация подходящей. это ломает это.

Проблема, которую вы можете получить, заключается в том, что мы не знаем, сколько времени понадобится потоку читателя, чтобы увидеть измененное значение. Если они находятся на разных процессорах с отдельными кешами, никаких гарантий нет, если только вы не используете барьер памяти для синхронизации кешей.

В x86 это выполняется автоматически с помощью аппаратного протокола, но не в некоторых других системах.

Нужно ли использовать какой-то механизм синхронизации (например, мьютексы) для защиты b?

Если вы этого не сделаете, у вас есть гонка данных. Программы с гонками данных имеют неопределенное поведение. Ответ на этот вопрос совпадает с ответом на вопрос "Хотите ли вы, чтобы ваша программа имела четко определенное поведение?"

Что может случиться, если я этого не сделаю?

Теоретически, все может случиться. Вот что значит неопределенное поведение. Наиболее вероятная плохая вещь, которая может случиться, состоит в том, что "вторая нить" может никогда не увидеть true значение.

Компилятор может предположить, что у программы нет гонок данных (если она имеет поведение, не определенное стандартом, поэтому поведение, как если бы оно не было нормальным). Поскольку второй поток когда-либо читает только из переменной, которая имеет значение falseи нет синхронизации, которая влияет на эти чтения, логический вывод состоит в том, что значение никогда не меняется, и, следовательно, цикл бесконечен. (и некоторые бесконечные циклы имеют неопределенное поведение в C++11!)

Вот несколько альтернативных решений:

  1. Используйте Mutex, детали были рассмотрены в других ответах выше.

  2. Подумайте об использовании блокировки чтения / записи, которая будет управлять / защищать одновременное чтение и запись. Библиотека pthread предоставляет реализацию: pthread_rwlock_t

  3. В зависимости от того, что делает ваше приложение, рассмотрите возможность использования условной переменной (pthread lib impl: pthread_cond_t). Фактически это сигнал от одного потока к другому, который может позволить вам удалить цикл while и проверку bool.

Достаточно сделать логическое значение volatile (для архитектуры x86) без мьютекса:

volatile bool b;
Другие вопросы по тегам