Есть ли преимущество использования ключевого слова volatile по сравнению с использованием класса Interlocked? - PullRequest
8 голосов
/ 09 ноября 2009

Другими словами, могу ли я сделать что-то с изменчивой переменной, которую также нельзя решить с помощью обычной переменной и класса Interlocked?

Ответы [ 5 ]

16 голосов
/ 09 ноября 2009

РЕДАКТИРОВАТЬ: вопрос в значительной степени переписан

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

  • volatile для чтения / записи должны быть защищены от переупорядочения. Это только означает чтение и запись, оно не означает любое другое действие;
  • volatility не принудительно устанавливается на ЦП, т. Е. На аппаратном уровне (x86 использует ограждения получения и выпуска на любое чтение / запись). Это предотвращает оптимизацию компилятора или CLR;
  • Interlocked использует атомарные инструкции по сборке для CompareExchange (cmpxchg), Increment (inc) и т. Д .;
  • Interlocked иногда использует блокировку: аппаратная блокировка в многопроцессорных системах ; в однопроцессорных системах аппаратная блокировка отсутствует;
  • Interlocked отличается от volatile тем, что использует полный забор , где volatile использует половину забора.
  • Чтение после записи может быть переупорядочено при использовании volatile. Это не может случиться с Interlocked. VolatileRead и VolatileWrite имеют ту же проблему переупорядочения, что и `volatile (ссылка благодаря Брайану Гидеону).

Теперь, когда у нас есть правила, мы можем определить ответ на ваш вопрос:

  • Технически: да, есть вещи, которые вы можете сделать с volatile, которые вы не можете сделать с Interlocked:
    1. Синтаксис: вы не можете писать a = b, где a или b является изменчивым, но это очевидно;
    2. Вы можете прочитать другое значение после записи его в переменную переменной из-за переупорядочения. Вы не можете сделать это с Interlocked. Другими словами: вы можете быть менее безопасным с volatile, тогда вы можете быть с Interlocked.
    3. Производительность: volatile быстрее, чем Interlocked.
  • Семантически: нет, потому что Interlocked просто предоставляет расширенный набор операций и безопаснее в использовании, поскольку применяется полное ограждение. Вы не можете ничего сделать с volatile, что вы не можете сделать с Interlocked, и вы можете сделать много с Interlocked, что вы не можете сделать с volatile:

    static volatile int x = 0;
    x++;                        // non-atomic
    static int y = 0;
    Interlocked.Increment(y);   // atomic
    
  • Область действия: да, объявление переменной volatile делает ее изменчивой для каждого отдельного доступа. Невозможно заставить это поведение другим способом, поэтому volatile нельзя заменить на Interlocked. Это необходимо в сценариях, где другие библиотеки, интерфейсы или оборудование могут обращаться к вашей переменной и обновлять ее в любое время, или требуется самая последняя версия.

Если вы спросите меня, этот последний бит действительно необходим для volatile и может сделать его идеальным, когда два процесса совместно используют память и нуждаются в чтении или записи без блокировки. Объявление переменной как volatile намного безопаснее в этом контексте, чем принуждение всех программистов использовать Interlocked (что не может быть принудительно установлено компилятором).


РЕДАКТИРОВАТЬ: Следующая цитата была частью моего первоначального ответа, я оставлю его; -)

Цитата из языка программирования C # стандарт:

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

  • Чтение летучего поля называется volatile read . Летучее чтение имеет: приобретать семантику "; то есть гарантированно произойдет до любого ссылки на память, которые происходят после это в последовательности команд.

  • Запись энергозависимого поля называется энергозависимая запись . Летучая запись имеет «релиз» семантика "; то есть это гарантированопроизойти после любых ссылок на память до инструкции по записи в последовательность команд.

Обновление: вопрос в значительной степени переписан, исправлен мой первоначальный ответ и добавлен "реальный" ответ

1 голос
/ 09 ноября 2009

Это довольно сложная тема. Я нахожу рецензию Джозефа Албахари одним из наиболее точных и точных источников многопоточности в .NET Framework, который может помочь ответить на ваш вопрос.

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

0 голосов
/ 09 ноября 2009

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

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

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

0 голосов
/ 09 ноября 2009

Я не буду пытаться быть авторитетом в этом вопросе, но я настоятельно рекомендую вам взглянуть на эту статью от хваленого Джона Скита.

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

0 голосов
/ 09 ноября 2009

Да - вы можете посмотреть на значение напрямую.

Пока вы используете ТОЛЬКО класс Interlocked для доступа к переменной, никакой разницы нет. volatile сообщает компилятору, что переменная является особенной, и при оптимизации не следует предполагать, что значение не изменилось.

Принимает этот цикл:

bool done = false;

...

while(!done)
{
... do stuff which the compiler can prove doesn't touch done...
}

Если вы установите done на true в другом потоке, вы ожидаете выхода из цикла. Однако - если выполнено, не помечено как volatile, тогда у компилятора есть возможность понять, что код цикла никогда не изменится done, и он может оптимизировать сравнение для выхода.

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

...