Модель памяти .NET, переменные и тестируемые переменные: что гарантировано? - PullRequest
10 голосов
/ 20 января 2010

Я знаю, что модель памяти .NET (на платформе .NET Framework; не компактная / micro / silverlight / mono / xna / what-have-you) гарантировала, что для определенных типов (особенно примитивных целых и ссылок) операции были гарантированно будет атомным.

Кроме того, я считаю, что инструкция x86 / x64 test-and-set (и Interlocked.CompareExchange) на самом деле ссылается на глобальную ячейку памяти, поэтому, если она завершится успешно, другой Interlocked.CompareExchange увидит новое значение.

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

Это приводит к нескольким вопросам:

  1. Верны ли мои убеждения выше?
  2. Interlocked.Read не имеет перегрузки для int, только для длинных (которые представляют собой 2 слова и, следовательно, обычно не читаются атомарно). Я всегда предполагал, что модель памяти .NET гарантирует, что новейшие значения будут видны при чтении целых / ссылок, однако с кэш-памятью процессора, регистрами и т. Д. Я начинаю понимать, что это может быть невозможно. Так есть ли способ заставить переменную быть повторно извлеченной?
  3. Достаточно ли летучего, чтобы решить вышеуказанную проблему для целых чисел и ссылок?
  4. На x86 / x64 можно предположить, что ...

Если есть две глобальные целочисленные переменные x и y, обе инициализируются равными 0, что если я напишу:

x = 1;
y = 2;

что НЕТ поток увидит x = 0 и y = 2 (т.е. записи будут происходить по порядку). Изменится ли это, если они изменчивы?

Ответы [ 3 ]

6 голосов
/ 23 января 2010
  • Только чтение и запись в переменные шириной не более 32 бит (и шириной 64 бита в системах x64) являются атомарными.Все это означает, что вы не прочитаете int и не получите половинное значение.Это не означает, что арифметика является атомарной.
  • Блокированные операции также действуют как барьеры памяти, поэтому да, Interlocked.CompareExchange увидит обновленное значение.
  • См. эту страницу .Летучий не значит заказанный.Некоторые компиляторы могут предпочесть не переупорядочивать операции с изменчивыми переменными, но ЦП может переупорядочивать их.Если вы хотите запретить процессору переупорядочивать инструкции, используйте (полный) барьер памяти.
  • Модель памяти гарантирует, что чтение и запись являются атомарными, а использование ключевого слова volatile гарантирует, что чтение будет всегда приходят из памяти, а не из регистра.Таким образом, вы увидите новейшее значение.Это связано с тем, что процессоры x86 при необходимости аннулируют кэш - см. this и this .Также см. InterlockedCompareExchange64 , чтобы узнать, как атомарно читать 64-битные значения.
  • И, наконец, последний вопрос.Ответ заключается в том, что поток на самом деле может видеть x = 0 и y = 2, и использование ключевого слова volatile не меняет этого, поскольку процессор может свободно переупорядочивать инструкции.Вам необходим барьер памяти.

Краткое описание:

  1. Компилятор может свободно переупорядочивать инструкции.
  2. Процессор может переупорядочиватьинструкции.
  3. Чтение и запись в формате Word являются атомарными.Арифметические и другие операции не являются атомарными, потому что они включают в себя чтение, вычисление, а затем запись.
  4. При чтении из памяти в формате Word всегда будет извлекаться самое новое значение.Но большую часть времени вы не знаете, действительно ли вы читаете из памяти.
  5. Полный барьер памяти останавливается (1) и (2).Большинство компиляторов позволяют вам самостоятельно останавливать (1).
  6. Ключевое слово volatile гарантирует, что вы читаете из памяти - (4).
  7. Блокированные операции (префикс блокировки) допускают несколько операцийбыть атомным.Например, чтение + запись (InterlockedExchange).Или чтение + сравнение + запись (InterlockedCompareExchange).Они также действуют как барьеры памяти, поэтому (1) и (2) останавливаются.Они всегда записывают в память (очевидно), поэтому (4) гарантировано.
2 голосов
/ 08 июня 2010

наткнулся на эту старую ветку.Все ответы Ханса и wj32 верны, за исключением части, касающейся volatile.

В частности, относительно вашего вопроса

На x86 / x64 могу ли я предположить, что ... Если естьдве глобальные целочисленные переменные x и y, оба инициализированы равными 0, что если я напишу: x = 1; y = 2;

Этот НЕТ поток увидит x = 0 и y = 2 (т.е. записи будут происходить по порядку).Изменится ли это, если они являются энергозависимыми?

Если y является энергозависимым, запись в x гарантированно произойдет перед записью в y, поэтому ни один поток никогда не увидит x = 0 и y = 2.Это связано с тем, что запись в энергозависимую переменную имеет «семантику релиза» (логически эквивалентную выдаче ограничителя деблокирования), то есть все инструкции чтения / записи, прежде чем она не будет перемещена, проходят ее.(Это означает, что если x изменчив, а y нет, вы все равно можете увидеть неожиданные x = 0 и y = 2.) См. Описание описания и кода в C # spec для получения более подробной информации.

0 голосов
/ 20 января 2010

Нет, ключевое слово volatile и гарантия атомарности слишком слабы. Вам нужен барьер памяти, чтобы гарантировать это. Вы можете получить его явно с помощью метода Thread.MemoryBarrier ().

...