Модель памяти .NET, изменчивые переменные и test-and-set: что гарантировано?

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

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

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

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

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

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

x = 1;
y = 2;

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

Ответ 1

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

Резюме:

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

Ответ 2

Перешел через этот старый поток. Ответы от Hans и wj32 правильны, за исключением части, касающейся volatile.

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

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

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

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

Ответ 3

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