2010-05-31 4 views
12

Joe Duffy дает 6 rules that describe the CLR 2.0+ memory model (это реальная реализация, а не стандарт ECMA) Я пишу свою попытку понять это, в основном, как способ резинового уклона, но если я ошибаюсь в моя логика, по крайней мере, кто-то здесь сможет поймать ее, прежде чем это вызовет мое горе.Общие сведения о модели памяти CLR 2.0

  • Правило 1: Зависимость данных между грузами и магазины не нарушаются.
  • Правило 2: Все магазины имеют семантику выпуска, i.e. Без груза или магазина может перемещаться после .
  • Правило 3: Все летучие грузы: приобретают, то есть нет груза или магазина могут перемещаться до одного.
  • Правило 4: Нет нагрузки и магазинов никогда не может пересечь полный барьер (например Thread.MemoryBarrier, блокировка ACQUIRE, Interlocked.Exchange, 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 может быть перемещен до, прежде чем нагрузки у, но в магазинах не могут быть повторно заказать все. Поэтому, если поток видит z == 0, то он также увидит x == y.

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

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

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

Я думаю, правило 6 означает, что если вы делаете:

x = y 
x = z 

Тогда можно для CLR, чтобы удалить и нагрузку у и первый магазин в х.

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

ответ

9

Джо Даффи обсуждает Правило 5 на pp517-18 из Concurrent Programming on 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-блоке может содержаться код, который предполагает значение, считанное в f, осталось 0, а введение чтения может сломать это предположение. В дополнение к , запрещающим это для изменчивых переменных , модель памяти .NET запрещает ее для обычных переменных , ссылаясь также на память кучи GC.

I blogged about one important place where this matters: стандартный шаблон для поднятия события.

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

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

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

+0

Приятное объяснение! Это объясняет, почему вы не хотите, чтобы CLR вводил нагрузки. Я не могу придумать, где компилятор/JIT может когда-нибудь захотеть представить магазины, не так ли? – Eloff

+0

Хорошо заслуженный +1. – Steven

+1

@ Eloff: Некоторые компиляторы могут переписать «if (cond) {x = y;}» как «x = y», если (! Cond) {x = old_x;} ", если они считают, что лучше было бы предсказать ветвь. Несмотря на отсутствие заметной разницы в однопоточном сценарии, это, очевидно, имеет плохие последствия, если «x» отображается для нескольких потоков, поэтому модель памяти CLR запрещает это. –

Смежные вопросы