Еще более изменчивый: необходимо предотвратить оптимизацию? - PullRequest
0 голосов
/ 18 февраля 2019

Я много читал о ключевом слове 'volatile', но у меня все еще нет однозначного ответа.

Рассмотрим этот код:

class A
{
public:
    void work()
    {
        working = true;

        while(working)
        {
            processSomeJob();
        }
    }

    void stopWorking() // Can be called from another thread
    {
        working = false;
    }
private:
    bool working;
}

Как работа ()входит в свой цикл, значение 'working' равно true.

  • Теперь я предполагаю, что компилятору разрешено оптимизировать , пока (работает) до while (true) , поскольку значение 'working' равно true при запуске цикла.

    • Если это не так, это будет означать, что что-то вроде этого будет совершенно неэффективно:
    for(int i = 0; i < someOtherClassMember; i++)
    {
        doSomething(); 
    }
    

    ... так как значение someOtherClassMember должно будетзагружаться каждую итерацию.

    • Если это , я бы подумал, что «рабочий» должен быть volatile , чтобы компилятор не оптимизировалэто.

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

Ответы [ 3 ]

0 голосов
/ 18 февраля 2019

Ваша программа будет оптимизирована в бесконечный цикл .

void foo() { A{}.work(); }

компилируется в (g ++ с O2)

foo():
        sub     rsp, 8
.L2:
        call    processSomeJob()
        jmp     .L2

Стандарт определяет, что гипотетическая абстрактная машина будет делать с программой.Стандартно-совместимые компиляторы должны компилировать вашу программу так, чтобы она работала так же, как и эта машина, во всех наблюдаемых режимах.Это известно как правило as-if , у компилятора есть свобода, пока то, что делает ваша программа, одинаково, независимо от how .

Как правило, чтение и запись в переменную не представляется настолько наблюдаемой, поэтому компилятор может исключить столько операций чтения и записи, сколько ему захочется.Компилятор может видеть, что working не назначен и оптимизирует чтение.Эффект (часто неправильно понимаемый) volatile заключается именно в том, чтобы сделать их видимыми, что заставляет компиляторы оставлять чтение и запись в одиночку .

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

TL; DR Не используйте обычные bool и volatile для многопоточности.Используйте std::atomic<bool>.

† Не во всех ситуациях.void bar(A& a) { a.work(); } не для некоторых версий.
‡ На самом деле, есть некоторые дебаты вокруг этого.

0 голосов
/ 18 февраля 2019

Теперь я предполагаю, что компилятору разрешено оптимизировать while (работает) до while (true)

Потенциально, да.Но только если он может доказать, что processSomeJob() не изменяет переменную working, т. Е. Если он может доказать, что цикл бесконечный.

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

Ваше рассуждение обоснованно.Однако место в памяти может оставаться в кэше, и чтение из кэша ЦП не обязательно значительно замедляется.Если doSomething достаточно сложен, чтобы вызвать изгнание someOtherClassMember из кеша, то, конечно, нам придется загружаться из памяти, но, с другой стороны, doSomething может быть настолько сложным, что загрузка одной памяти незначительна всравнение.

Какой из этих двух случаев?

Либо.Оптимизатор не сможет анализировать все возможные пути кода;Мы не можем предполагать, что цикл может быть оптимизирован во всех случаях.Но если someOtherClassMember доказуемо не изменяется ни в каких путях кода, то доказать это было бы возможно теоретически, и, следовательно, цикл можно оптимизировать теоретически.

, но я также нахожу утверждения, что [volatile] следует использовать в сценарии, подобном моему.

volatile здесь вам не поможет.Если working изменено в другом потоке, то происходит гонка данных.А гонка данных означает, что поведение программы не определено.

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

0 голосов
/ 18 февраля 2019

Volatile заставит цикл while перезагружать переменную working при каждой проверке.На практике это часто позволяет вам останавливать работающую функцию с помощью вызова stopWorking, сделанного из асинхронного обработчика сигнала или другого потока, но по стандарту этого недостаточно.Стандарт требует атомы без блокировки или переменные типа volatile sig_atomic_t для обычной связи контекста <-> и атомика для связи между потоками.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...