Считается ли volatile bool для управления потоками неправильным?

В результате моего ответа на этот вопрос я начал читать о ключевом слове volatile ] и каков консенсус по этому поводу. Я вижу, что об этом много информации, часть старой, которая сейчас кажется неправильной, и много новой, которая говорит, что ей почти нет места в многопоточном программировании. Следовательно, я хотел бы прояснить конкретное использование (не могу найти здесь точного ответа на SO).

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

Допустим, у нас есть такой класс:

class SomeWorker
{
public:
    SomeWorker() : isRunning_(false) {}
    void start() { isRunning_ = true; /* spawns thread and calls run */ }
    void stop() { isRunning_ = false; }

private:
    void run()
    {
        while (isRunning_)
        {
            // do something
        }
    }
    volatile bool isRunning_;
};

Для простоты некоторые вещи опущены, но самое главное - создается объект, который что-то делает во вновь созданном потоке, проверяя ( volatile ) логическое значение, чтобы знать, следует ли его остановить. Это логическое значение устанавливается из другого потока всякий раз, когда он хочет, чтобы рабочий процесс остановился.

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

Я хотел бы понять, считается ли это совершенно неправильным, и правильным ли подходом является использование синхронизированной переменной? Есть ли разница между компилятором / архитектурой / ядрами? Может быть, это просто небрежный подход, которого стоит избегать?

Я был бы рад, если бы кто-нибудь прояснил это. Спасибо!

ИЗМЕНИТЬ

Мне было бы интересно увидеть (в коде), как вы решите эту проблему.

27
задан Community 23 May 2017 в 11:48
поделиться

2 ответа

Использование volatile достаточно только для одного ядра, где все потоки используют один и тот же кеш. На многоядерных процессорах, если stop() вызывается на одном ядре, а run() выполняется на другом, для синхронизации кэшей ЦП может потребоваться некоторое время, что означает, что два ядра могут видеть два разных представления isRunning_. Это означает, что run() будет работать некоторое время после его остановки.

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

6
ответ дан 28 November 2019 в 05:40
поделиться

Это будет работать для вашего случая, но для защиты критического раздела этот подход неверен. Если бы это было правильно, то можно использовать изменчивый bool почти во всех случаях, когда используется мьютекс. Причина этого в том, что переменная переменная не гарантирует применения каких-либо барьеров памяти или какого-либо механизма согласованности кэша. Наоборот, мьютекс делает. Другими словами, как только мьютекс заблокирован, аннулирование кэша передается всем ядрам, чтобы поддерживать согласованность между всеми ядрами. С изменчивым это не тот случай. Тем не менее, Андрей Александреску предложил очень интересный подход для использования volatile для обеспечения синхронизации на общем объекте. И, как вы увидите, он делает это с мьютексом; volatile используется только для предотвращения доступа к интерфейсу объекта без синхронизации.

0
ответ дан 28 November 2019 в 05:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: