Кто-нибудь может дать хорошее объяснение ключевого слова volatile в C #? Какие проблемы он решает, а какие нет? В каких случаях это спасет меня от использования блокировки?
c#
multithreading
Дорон Яакоби
источник
источник
Ответы:
Я не думаю, что есть лучший человек, чтобы ответить на это, чем Эрик Липперт (акцент в оригинале):
Для дальнейшего чтения смотрите:
источник
volatile
будут существовать благодаря замкуЕсли вы хотите немного больше узнать о том, что делает ключевое слово volatile, рассмотрите следующую программу (я использую DevStudio 2005):
Используя стандартные оптимизированные (релизные) параметры компилятора, компилятор создает следующий ассемблер (IA32):
Глядя на вывод, компилятор решил использовать регистр ecx для хранения значения переменной j. Для энергонезависимого цикла (первого) компилятор назначил i в регистр eax. Довольно просто. Однако есть пара интересных битов - инструкция lea ebx, [ebx] фактически является многобайтовой nop-инструкцией, поэтому цикл переходит к 16-байтовому выровненному адресу памяти. Другое - использование edx для увеличения счетчика цикла вместо использования команды inc eax. Команда add reg, reg имеет меньшую задержку на нескольких ядрах IA32 по сравнению с инструкцией inc reg, но никогда не имеет большей задержки.
Теперь для цикла со счетчиком изменчивых циклов. Счетчик хранится в [esp], а ключевое слово volatile сообщает компилятору, что значение всегда должно считываться / записываться в память и никогда не присваиваться регистру. Компилятор даже заходит так далеко, что не выполняет загрузку / приращение / сохранение как три отдельных шага (загрузка eax, inc eax, save eax) при обновлении значения счетчика, вместо этого память напрямую изменяется в одной инструкции (добавление памяти). , р). Способ создания кода гарантирует, что значение счетчика цикла всегда актуально в контексте одного ядра ЦП. Никакая операция с данными не может привести к повреждению или потере данных (следовательно, не используется load / inc / store, поскольку значение может измениться во время inc, таким образом теряясь в хранилище). Поскольку прерывания могут обслуживаться только после завершения текущей инструкции,
После того, как вы введете в систему второй ЦП, ключевое слово volatile не защитит данные, обновляемые одновременно другим ЦП. В приведенном выше примере вам понадобится выровнять данные, чтобы получить потенциальное повреждение. Ключевое слово volatile не предотвратит потенциальное повреждение, если данные не могут быть обработаны атомарно, например, если счетчик цикла имел тип long long (64 бита), то для обновления значения потребовались бы две 32-битные операции, в середине какое прерывание может произойти и изменить данные.
Таким образом, ключевое слово volatile подходит только для выровненных данных, размер которых меньше или равен размеру собственных регистров, так что операции всегда являются атомарными.
Ключевое слово volatile было задумано для использования с операциями ввода-вывода, в которых ввод-вывод постоянно менялся, но имел постоянный адрес, такой как устройство UART с отображением в памяти, и компилятору не следует повторно использовать первое значение, считанное с адреса.
Если вы обрабатываете большие данные или у вас несколько процессоров, вам потребуется система блокировки более высокого уровня (OS) для правильной обработки доступа к данным.
источник
Если вы используете .NET 1.1, ключевое слово volatile необходимо при двойной проверке блокировки. Зачем? Поскольку до .NET 2.0 следующий сценарий мог заставить второй поток обращаться к ненулевому, но не полностью сконструированному объекту:
До версии .NET 2.0 this.foo мог быть назначен новый экземпляр Foo до завершения работы конструктора. В этом случае может прийти второй поток (во время вызова потока 1 для конструктора Foo) и испытать следующее:
До версии .NET 2.0 вы могли объявить this.foo нестабильным, чтобы обойти эту проблему. Начиная с .NET 2.0 вам больше не нужно использовать ключевое слово volatile для двойной блокировки.
В Википедии на самом деле есть хорошая статья о блокировке с двойной проверкой, и она кратко затрагивает эту тему: http://en.wikipedia.org/wiki/Double-checked_locking
источник
foo
? Разве поток 1 не блокируетсяthis.bar
и, следовательно, только поток 1 сможет инициализировать foo в данный момент времени? Я имею в виду, что вы проверяете значение послеИногда компилятор оптимизирует поле и использует регистр для его хранения. Если поток 1 выполняет запись в поле и другой поток обращается к нему, так как обновление было сохранено в регистре (а не в памяти), 2-й поток получит устаревшие данные.
Вы можете подумать о ключевом слове volatile, которое говорит компилятору: «Я хочу, чтобы вы сохранили это значение в памяти». Это гарантирует, что 2-й поток извлекает последнее значение.
источник
Из MSDN : модификатор volatile обычно используется для поля, к которому обращаются несколько потоков, без использования оператора блокировки для сериализации доступа. Использование модификатора volatile гарантирует, что один поток получит самое последнее значение, записанное другим потоком.
источник
CLR любит оптимизировать инструкции, поэтому при доступе к полю в коде он не всегда может получить доступ к текущему значению поля (оно может быть из стека и т. Д.). Пометка поля as
volatile
обеспечивает доступ к текущему значению поля с помощью инструкции. Это полезно, когда значение может быть изменено (в неблокирующем сценарии) параллельным потоком в вашей программе или другим кодом, работающим в операционной системе.Вы, очевидно, теряете некоторую оптимизацию, но она делает код более простым.
источник
Я нашел эту статью Joydip Kanjilal очень полезной!
When you mark an object or a variable as volatile, it becomes a candidate for volatile reads and writes. It should be noted that in C# all memory writes are volatile irrespective of whether you are writing data to a volatile or a non-volatile object. However, the ambiguity happens when you are reading data. When you are reading data that is non-volatile, the executing thread may or may not always get the latest value. If the object is volatile, the thread always gets the most up-to-date value
Я просто оставлю это здесь для справки
источник
Компилятор иногда меняет порядок операторов в коде, чтобы оптимизировать его. Обычно это не проблема в однопоточной среде, но это может быть проблемой в многопоточной среде. Смотрите следующий пример:
Если вы запустите t1 и t2, вы не ожидаете вывода или «Value: 10» в качестве результата. Возможно, компилятор переключает строку внутри функции t1. Если затем выполняется t2, возможно, значение _flag равно 5, а значение _value равно 0. Таким образом, ожидаемая логика может быть нарушена.
Чтобы исправить это, вы можете использовать ключевое слово volatile, которое вы можете применить к полю. Этот оператор отключает оптимизацию компилятора, поэтому вы можете установить правильный порядок в вашем коде.
Вы должны использовать volatile только в том случае, если оно действительно вам нужно, потому что оно отключает определенные оптимизации компилятора, оно снижает производительность Он также не поддерживается всеми языками .NET (Visual Basic не поддерживает его), поэтому он препятствует взаимодействию языков.
источник
Итак, чтобы подвести итог всего этого, правильный ответ на вопрос таков: если ваш код выполняется во время выполнения 2.0 или более поздней версии, ключевое слово volatile почти никогда не требуется и приносит больше вреда, чем пользы, если используется без необходимости. IE никогда не используй это. НО в более ранних версиях среды выполнения это необходимо для правильной двойной проверки блокировки статических полей. В частности, статические поля, класс которых имеет код инициализации статического класса.
источник
несколько потоков могут получить доступ к переменной. Последнее обновление будет в переменной
источник