Модель памяти.NET, переменные и тестируемые переменные: что гарантировано?
Я знаю, что модель памяти.NET (в.NET Framework; не компактная /micro/silverlight/mono/xna/what-have-you) гарантировала, что для определенных типов (особенно примитивных целых и ссылок) операции гарантированно будут атомное.
Кроме того, я считаю, что инструкция тестирования и установки x86/x64 (и Interlocked.CompareExchange
) на самом деле ссылается на место в глобальной памяти Interlocked.CompareExchange
увидит новое значение.
Наконец, я считаю, что volatile
Ключевое слово - это инструкция для компилятора распространять операции чтения и записи как можно скорее и не переупорядочивать операции с этой переменной (верно?).
Это приводит к нескольким вопросам:
- Верны ли мои убеждения выше?
Interlocked.Read
не имеет перегрузки для int, только для длинных (которые представляют собой 2 слова и, следовательно, обычно не читаются атомарно). Я всегда предполагал, что модель памяти.NET гарантирует, что новейшие значения будут видны при чтении целых / ссылок, однако с кэш-памятью процессора, регистрами и т. Д. Я начинаю понимать, что это может быть невозможно. Так есть ли способ заставить переменную быть переизбранной?- Достаточно ли изменчиво, чтобы решить вышеупомянутую проблему для целых чисел и ссылок?
- На 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 не меняет этого, поскольку процессор может свободно переупорядочивать инструкции. Вам нужен барьер памяти.
Резюме:
- Компилятор свободен, чтобы переупорядочить инструкции.
- Процессор свободен, чтобы переупорядочить инструкции.
- Чтение и запись в формате Word являются атомарными. Арифметические и другие операции не являются атомарными, потому что они включают чтение, вычисление, а затем запись.
- При чтении из памяти в формате Word всегда извлекаются новейшие значения. Но большую часть времени вы не знаете, читаете ли вы по памяти.
- Полный барьер памяти останавливается (1) и (2). Большинство компиляторов позволяют вам самостоятельно останавливать (1).
- Ключевое слово volatile гарантирует, что вы читаете из памяти - (4).
- Блокированные операции (префикс блокировки) позволяют нескольким операциям быть атомарными. Например, чтение + запись (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().