Понимание модели памяти CLR 2.0 - PullRequest
13 голосов
/ 31 мая 2010

Джо Даффи, дает 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.

Если у был энергозависимым, то нагрузка 0 не могла двигаться до нагрузки у, иначе это может произойти. Волатильные магазины, похоже, не обладают какими-либо особыми свойствами, ни один магазин не может быть переупорядочен по отношению друг к другу (что является очень сильной гарантией!)

Полные барьеры похожи на линию в песке, по которой грузы и склады нельзя перемещать.

Понятия не имею, что означает правило 5.

Полагаю, правило 6 означает, что вы делаете:

x = y
x = z

Тогда CLR может удалить как загрузку в y, так и первое сохранение в 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

Что, если у летучий? Я не вижу в правилах ничего, что запрещало бы выполнение вышеуказанной оптимизации. Это не нарушает двойной проверки блокировки, поскольку lock () между двумя одинаковыми условиями предотвращает перемещение грузов в смежные позиции, и, согласно правилу 6, это единственный раз, когда они могут быть устранены.

Так что я думаю, что понимаю все, кроме правила 5, здесь. Кто-нибудь хочет просветить меня (или исправить меня, или добавить что-нибудь к чему-либо из вышеперечисленного?)

1 Ответ

10 голосов
/ 31 мая 2010

Джо Даффи обсуждает правило 5 на pp517-18 Параллельное программирование в Windows :

В качестве примера, когда нагрузка может быть введен, рассмотрим этот код:

MyObject mo = ...;
int f = mo.field;
if (f == 0)
{
    // do something
    Console.WriteLine(f);
}

Если промежуток времени между начальным читать mo.field в переменную f и последующее использование F в Console.WriteLine был достаточно длинным, Компилятор может решить, что будет больше Эффективно перечитать mo.field дважды. ... Это будет проблемой, если Мо - это куча объектов и потоков писать одновременно с мо.филдом. Блок if может содержать код, который предполагает значение, считанное в f, осталось 0, и введение чтения может сломаться это предположение. В дополнение к запрещающий это для летучих переменные, модель памяти .NET запрещает это для обычных переменных также относится к памяти кучи GC.

Я написал в блоге об одном важном месте, где это имеет значение : стандартная схема возбуждения события.

EventHandler handler = MyEvent;
if (handler != null)
    handler(this, EventArgs.Empty);

Чтобы избежать проблем с удалением обработчика событий в отдельном потоке, мы читаем текущее значение MyEvent и вызываем обработчики событий только в том случае, если этот делегат не равен нулю.

Если можно было выполнить чтение из кучи, компилятор / JIT мог бы решить, что может быть лучше снова прочитать MyEvent, чем использовать локальное, что приведет к состоянию гонки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...