Подтвердить что ты не робот

Понимание модели памяти 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, здесь. Кто-нибудь хочет просветить меня (или исправить меня или добавить что-нибудь к любому из вышеперечисленного?)

4b9b3361

Ответ 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, а не использовать локальный, который бы ввел условие гонки.