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

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

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

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

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

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

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

x = 1;
y = 2;

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

3 ответа

Решение
  • Только чтение и запись в переменные, которые имеют максимальную ширину 32 бита (и 64 бита в системах x64), являются атомарными. Все это означает, что вы не будете читать int и получать наполовину записанное значение. Это не значит, что арифметика атомарна.
  • Блокированные операции также действуют как барьеры памяти, так что да, Interlocked.CompareExchange увидит обновленное значение.
  • Смотрите эту страницу. Летучий не значит заказанный. Некоторые компиляторы могут не переупорядочивать операции с изменчивыми переменными, но ЦП может переупорядочивать их. Если вы хотите помешать процессору переупорядочивать инструкции, используйте (полный) барьер памяти.
  • Модель памяти гарантирует, что чтение и запись являются атомарными, а использование ключевого слова volatile гарантирует, что чтение всегда будет происходить из памяти, а не из регистра. Таким образом, вы увидите новейшую ценность. Это связано с тем, что процессоры x86 при необходимости аннулируют кэш - посмотрите это и это. Также см. 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) гарантировано.

Наткнулся на эту старую ветку. Ответы от Ганса и WJ32 все правильные, за исключением части, касающейся volatile,

Конкретно по вашему вопросу

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

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

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

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

Другие вопросы по тегам