Использование volatile-спецификатора в C / C ++ / Java - PullRequest
5 голосов
/ 30 июня 2009

При просмотре множества ресурсов по многопоточному программированию обычно появляется ссылка на volatile-спецификатор. Ясно, что использование этого ключевого слова не является надежным способом достижения синхронизации между несколькими потоками по крайней мере в C / C ++ и Java (версии 1.4 и ранее). Вот что википедия перечисляет (без объяснения как) как типичное использование этого спецификатора: -

  1. разрешить доступ к отображенным в память устройствам
  2. разрешить использование переменных между setjmp и longjmp
  3. разрешить использование переменных в обработчиках сигналов
  4. занят ожидания

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

Может кто-нибудь объяснить?

Ответы [ 8 ]

12 голосов
/ 30 июня 2009

Ваш вопрос технически известен как «банка червей»! Для c / c ++ (я не могу комментировать Java)
Вы можете очень грубо резюмировать volatile как указание компилятору «пожалуйста, не оптимизируйте это», но среди профессионалов много споров о том, является ли это
а) На всех полезных для уровня ядра кодах <-редактировано уточнено на основе отзывов <br> б) Даже правильно реализовано большинством компиляторов.

Кроме того, никогда не используйте его для многопоточного программирования, и очень хорошее объяснение, почему

= Edit = Интересно, за что это стоит. Деннис Ритчи был против его включения (а также const) детали здесь

6 голосов
/ 30 июня 2009

Поскольку вас интересуют эти варианты использования, я объясню первый. Обратите внимание, что это применимо с точки зрения c / c ++, но я не уверен, как это работает в java, хотя я подозреваю, что в целом нестабильность в c / c ++ и java используется для совершенно разных случаев.

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

Предположим, у вас есть немного света с таймером, который отображен в памяти. Вы включаете свет, записывая 1 в его адрес памяти, и его внутренний таймер ведет обратный отсчет в течение 5 секунд, выключает свет и сбрасывает ячейку памяти на 0. Теперь вы разрабатываете программу переменного тока, которая должна включить этот свет после определенных событий. и иногда выключайте его до истечения срока действия счетчика. Если вы используете обычную переменную (обычно указатель или ссылку для этого типа приложения) для записи в ее область памяти, существует ряд вещей, которые могут пойти не так из-за оптимизации компилятора.

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

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

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

4 голосов
/ 30 июня 2009

Мне показалась эта статья DDJ Херба Саттера очень интересной, особенно то, как трактуется volatile в C ++, Java и C # .NET.

д-р Доббс летучий и летучий

2 голосов
/ 01 июля 2009

Изменчивые переменные полезны в Java (по крайней мере, начиная с Java 5.0, где их поведение изменилось ), как говорит Брайан Гетц в своей книге «Параллелизм Java на практике» (JCIP) - важная книга по этому вопросу (стр 37):

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

Явная синхронизация также может достичь этого, но часто мы не всегда хотим заблокировать значение. Двойной проверкой блокировки является классический пример этого (скопировано из Wikipedia ):

// Works with acquire/release semantics for volatile
// Broken under Java 1.4 and earlier semantics for volatile
class Foo {
    private volatile Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            synchronized(this) {
                if (null == helper)
                    helper = new Helper();
            }
        }
        return helper;
    }

    // other functions and members...
}

Это не сработало бы, если бы помощник не был изменчивым.

Изменчивые переменные также могут использоваться для реализации неблокирующих параллельных структур данных, таких как java.util.concurrent.ConcurrentHashMap (который поддерживает одновременные обновления и доступ без блокировки - см. Исходный код JDK для использования летучих компонентов).

В JCIP подробно обсуждается блокировка с двойной проверкой, изменчивые переменные и параллелизм Java в целом. "Эффективная Ява", 2-е издание Джошуа Блоха также стоит прочитать.

Также обратите внимание, что атомарные переменные поддерживаются в Java в пакете java.util.concurrent.atomic . Это позволяет сделать изменения значений видимыми в потоках / процессорах аналогично изменчивым переменным, но также позволяет выполнять операции «Сравнить и установить», что означает, что некоторые дополнительные типы параллельных операций можно безопасно выполнять без блокировки.

2 голосов
/ 30 июня 2009

Здесь есть хорошее объяснение: http://en.wikipedia.org/wiki/Volatile_variable, но немного упрощенно, оно говорит компилятору, что он не должен предполагать, что переменная не доступна кому-то еще и что фатально оптимизировать ее в регистраторе обновлять только регистр, а не фактическое хранилище.

1 голос
/ 30 июня 2009

Ключевое слово volatile появилось давно в C, и в основном оно «отключает» некоторые оптимизации компилятора, которые предполагают, что если переменная не была явно изменена, она вообще не изменялась. В те дни его основной утилитой было объявление переменных, которые будут изменены обработчиками прерываний. Я, например, использовал его один раз (в конце 80-х) для глобальной переменной, содержащей положение курсора мыши. Положение было изменено с помощью прерывания, и без использования volatile основная программа иногда не обнаруживала бы его изменения, потому что компилятор оптимизировал доступ к переменной, думая, что в этом нет необходимости.

Сегодня это использование, как правило, устарело (если вы не пишете низкоуровневый код ОС), но все же есть некоторые редкие ситуации, в которых volatile полезен (очень редко - действительно, я, например, вероятно, не использовал его за последние 7 лет).

Но для многопоточного программирования это совершенно не рекомендуется. Проблема в том, что он не будет защищать одновременный доступ между потоками, он будет удалять только те оптимизации, которые не позволят его «обновить» в том же потоке. Он не предназначен для использования в многопоточных средах. Если вы на Java, используйте синхронизированный. Если вы находитесь в C ++, используйте некоторую библиотеку синхронизации, такую ​​как pthreads или Boost.Threads (или, что еще лучше, используйте новые библиотеки потоков C ++ 0X, если можете).

0 голосов
/ 30 июня 2009

Прошло много времени с тех пор, как я начал заниматься C ++, и я действительно не помню определение понятия «волатин» в этом языке. Но спецификация языка Java, в частности, гласит, что целью volatile является облегчение многопоточного доступа к переменной. Цитата: «Поле может быть объявлено как volatile, и в этом случае модель памяти Java (§17) гарантирует, что все потоки увидят непротиворечивое значение для переменной». Далее они говорят, что ссылки на переменные значения гарантированно будут выполняться в том порядке, в котором они указаны в коде, то есть если вы объявите i и j volatile, а затем напишите «++ i; ++ j», то i фактически всегда будет увеличиваться до j.

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

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

0 голосов
/ 30 июня 2009

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

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

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

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