Когда использовать volatile с многопоточностью?

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

Итак, каково использование /назначение volatile в многопоточной программе?

106 голосов | спросил David Preston 30 ThuEurope/Moscow2010-12-30T00:24:02+03:00Europe/Moscow12bEurope/MoscowThu, 30 Dec 2010 00:24:02 +0300 2010, 00:24:02

4 ответа


0

Short & быстрый ответ : volatile (почти) бесполезен для многопоточного прикладного программирования, не зависящего от платформы. Он не обеспечивает никакой синхронизации, не создает заборов памяти и не обеспечивает порядок выполнения операций. Это не делает операции атомарными. Это не делает ваш код волшебным потокобезопасным. volatile может быть единственным неправильно понятым средством во всем C ++. Смотрите это , this и this для получения дополнительной информации о volatile

С другой стороны, volatile имеет некоторое использование, которое может быть не столь очевидным. Его можно использовать почти так же, как можно было бы использовать const, чтобы помочь компилятору показать вам, где вы могли ошибиться при доступе к некоторым общий ресурс незащищенным способом. Это использование обсуждается Александреску в этой статье . Тем не менее, это в основном использует систему типов C ++ таким образом, что часто рассматривается как выдумка и может вызвать неопределенное поведение.

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

В стандарте C ++ 2003 года не говорится, что volatile применяет любой тип семантики Acquire или Release к переменным. На самом деле, стандарт полностью ничего не говорит о многопоточности. Однако определенные платформы применяют семантику Acquire и Release к переменным volatile.

[Обновление для C ++ 11]

Стандарт C ++ 11 теперь делает распознает многопоточность непосредственно в модели памяти и языке и предоставляет библиотечные средства для работы с ней независимо от платформы. Однако семантика volatile все еще не изменилась. volatile по-прежнему не является механизмом синхронизации. Бьярн Страуструп говорит так много в TCPPPL4E:

  

Не используйте volatile, за исключением низкоуровневого кода, который работает напрямую   с оборудованием.

     

Не думайте, что volatile имеет особое значение в модели памяти. Это   не. Это не - как в некоторых более поздних языках -   механизм синхронизации. Чтобы получить синхронизацию, используйте atomic, a   mutex или condition_variable.

[/конец обновления]

Все вышеизложенное относится к самому языку C ++, как определено стандартом 2003 года (а теперь и стандартом 2011 года). Однако некоторые конкретные платформы добавляют дополнительную функциональность или ограничения к тому, что делает volatile. Например, в MSVC 2010 (по крайней мере) семантика получения и выпуска do применяется к определенным операциям над переменными volatile , из MSDN :

  

При оптимизации компилятор должен поддерживать порядок среди ссылок   к изменчивым объектам, а также ссылки на другие глобальные объекты. В   частности,

     

Запись в энергозависимый объект (volatile write) имеет семантику Release;   ссылка на глобальный или статический объект, который происходит перед записью в   изменчивый объект в последовательности команд будет происходить до этого   изменчивая запись в скомпилированномдвоичный.

     

Чтение летучего объекта (volatile read) имеет семантику Acquire;   ссылка на глобальный или статический объект, который возникает после чтения   энергозависимая память в последовательности команд появится после этого   volatile читается в скомпилированном двоичном файле.

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

ответил John Dibling 30 ThuEurope/Moscow2010-12-30T00:31:24+03:00Europe/Moscow12bEurope/MoscowThu, 30 Dec 2010 00:31:24 +0300 2010, 00:31:24
0

Volatile иногда полезен по следующей причине: этот код:

/* global */ bool flag = false;

while (!flag) {}

оптимизирован gcc для:

if (!flag) { while (true) {} }

Что, очевидно, неверно, если флаг записан другим потоком. Обратите внимание, что без этой оптимизации механизм синхронизации, вероятно, сработает (в зависимости от другого кода могут потребоваться некоторые барьеры памяти) - нет необходимости в мьютексе в сценарии 1 производитель - 1 потребитель.

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

ответил zeuxcg 30 ThuEurope/Moscow2010-12-30T00:30:47+03:00Europe/Moscow12bEurope/MoscowThu, 30 Dec 2010 00:30:47 +0300 2010, 00:30:47
0

Вам нужна изменчивость и, возможно, блокировка.

volatile сообщает оптимизатору, что значение может изменяться асинхронно, поэтому

volatile bool flag = false;

while (!flag) {
    /*do something*/
}

будет читать флаг каждый раз вокруг цикла.

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

Блокировка является частью программы. Так, кстати, если вы реализуете семафоры, то среди прочего они должны быть нестабильными. (Не пытайтесь, это сложно, возможно, понадобится немного ассемблера или новый атомарный материал, и это уже сделано.)

ответил ctrl-alt-delor 31 FriEurope/Moscow2010-12-31T17:44:56+03:00Europe/Moscow12bEurope/MoscowFri, 31 Dec 2010 17:44:56 +0300 2010, 17:44:56
0
#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;

bool checkValue = false;

int main()
{
    std::thread writer([&](){
            sleep(2);
            checkValue = true;
            std::cout << "Value of checkValue set to " << checkValue << std::endl;
        });

    std::thread reader([&](){
            while(!checkValue);
        });

    writer.join();
    reader.join();
}

Однажды интервьюер, который также считал, что volatile бесполезен, поспорил со мной, что оптимизация не вызовет каких-либо проблем, и имел в виду разные ядра, имеющие отдельные строки кэша и все такое (на самом деле не понимал, что именно он имел в виду) , Но этот фрагмент кода при компиляции с -O3 на g ++ (g ++ -O3 thread.cpp -lpthread) показывает неопределенное поведение. В основном, если значение установлено перед проверкой while, оно работает нормально, а если нет, оно входит в цикл, не удосужившись извлечь значение (которое фактически было изменено другим потоком). По сути, я считаю, что значение checkValue выбирается только один раз в регистр и никогда не проверяется снова при самом высоком уровне оптимизации. Если перед извлечением установлено значение true, он работает нормально, а если нет, то зацикливается. Пожалуйста, поправьте меня, если я ошибаюсь.

ответил Anu Siril 11 J000000Wednesday18 2018, 09:58:17

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

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132