Понимание модели памяти CLR 2.0
Joe Duffy дает 6 правил, описывающих модель памяти CLR 2.0+ (это фактическая реализация, а не любой стандарт ECMA) Я пишу с моей попытки понять это, в основном, как способ резинового уклонения, но если я ошибусь в своей логике, по крайней мере, кто-то здесь сможет поймать его, прежде чем это вызовет мое горе.
- Правило 1: Зависимость данных от нагрузки
и магазины никогда не нарушаются.
- Правило 2: У всех магазинов есть семантика выпуска,
т.е. загрузка или хранение могут перемещаться после
один.
- Правило 3: Все летучие нагрузки
приобретать, то есть без нагрузки или хранения
двигаться до одного.
- Правило 4: Нет нагрузок и
магазины могут когда-либо пересекать полный барьер
(например, Thread.MemoryBarrier, блокировка
приобретать, блокировать. Обмен,
Interlocked.CompareExchange и т.д.).
- Правило 5: загрузка и хранение в кучу
никогда не могут быть введены.
- Правило 6:
Загрузка и сохранение могут быть удалены только
при объединении смежных нагрузок и
магазины из/в том же месте.
Я пытаюсь понять эти правила.
x = y
y = 0 // Cannot move before the previous line according to Rule 1.
x = y
z = 0
// equates to this sequence of loads and stores before possible re-ordering
load y
store x
load 0
store z
Посмотрев на это, кажется, что нагрузка 0 может быть перенесена до загрузки y, но магазины не могут быть повторно заказаны вообще. Поэтому, если поток видит z == 0, тогда он также увидит x == y.
Если y было изменчивым, тогда загрузка 0 не могла перемещаться до загрузки y, иначе это может произойти. Волатильные магазины, похоже, не имеют каких-либо специальных свойств, никакие магазины не могут быть переупорядочены по отношению друг к другу (что является очень сильной гарантией!)
Полные барьеры подобны линии в песке, которые загружаются, а магазины не могут перемещаться.
Не знаю, что означает правило 5.
Я предполагаю, что правило 6 означает, что вы делаете:
x = y
x = z
Тогда CLR может удалить как нагрузку, так и первую ячейку памяти до x.
x = y
z = y
// equates to this sequence of loads and stores before possible re-ordering
load y
store x
load y
store z
// could be re-ordered like this
load y
load y
store x
store z
// rule 6 applied means this is possible?
load y
store x // but don't pop y from stack (or first duplicate item on top of stack)
store z
Что делать, если y была изменчивой? Я не вижу ничего в правилах, которые запрещают выполнение вышеуказанной оптимизации. Это не нарушает блокировку с двойной проверкой, потому что блокировка() между двумя идентичными условиями предотвращает перемещение нагрузок в смежные позиции и согласно правилу 6, что единственный раз, когда они могут быть устранены.
Итак, я думаю, что я понимаю все, кроме правила 5, здесь. Кто-нибудь хочет просветить меня (или исправить меня или добавить что-нибудь к любому из вышеперечисленного?)
Ответы
Ответ 1
Джо Даффи обсуждает правило 5 на стр. 17-18 из Параллельное программирование в Windows:
В качестве примера, когда может быть загрузка, рассмотрим этот код:
MyObject mo = ...;
int f = mo.field;
if (f == 0)
{
// do something
Console.WriteLine(f);
}
Если период времени между начальным чтение mo.field в переменную f и последующее использование f в Console.WriteLine был достаточно длинным, компилятор может решить, что это будет больше эффективно перечитывать mo.field дважды.... Выполнение этого было бы проблемой, если бы mo - объект кучи, а потоки - написание одновременно с mo.field. if-block может содержать код, который предполагает значение, считанное в f, осталось 0, и введение чтения может сломаться это предположение. В дополнении к запрещение этого для изменчивых переменные, модель памяти .NET запрещает его для обычных переменных ссылаясь также на память кучи GC.
I размещено в блоге об одном важном месте, где это имеет значение: стандартный шаблон для создания события.
EventHandler handler = MyEvent;
if (handler != null)
handler(this, EventArgs.Empty);
Чтобы предотвратить проблемы с удалением обработчика событий в отдельном потоке, мы считываем текущее значение MyEvent
и вызываем только обработчики событий, если этот делегат не равен нулю.
Если можно было бы прочитать данные из кучи, компилятор /JIT может решить, что лучше было бы лучше читать MyEvent
, а не использовать локальный, который бы ввел условие гонки.