class BoxedInt
{
public int Value { get; set; }
}
class LazyInit
{
volatile BoxedInt _box;
public int LazyGet()
{
var b = _box; // Read 1if (b == null)
{
lock(this)
{
b = new BoxedInt();
b.Value = 42; // Write 1
_box = b; // Write 2
}
}
return b.Value; // Read 2
}
}
Утверждается следующее:
In this example, LazyGet is always guaranteed to return “42.” However, if the _box field were not volatile, LazyGet would be allowed to return “0” for two reasons: the reads could get reordered, or the writes could get reordered.
C "writes" все понятно, но как в данном случае могут быть переупорядочены (reordered) "Read 1" и "Read 2" так, чтобы вернулось 0, при том, что writes не переупорядочены?
Здравствуйте, mrTwister, Вы писали:
T>Утверждается следующее: T>
In this example, LazyGet is always guaranteed to return “42.” However, if the _box field were not volatile, LazyGet would be allowed to return “0” for two reasons: the reads could get reordered, or the writes could get reordered.
Здравствуйте, Sharov, Вы писали:
S>Здравствуйте, mrTwister, Вы писали:
T>>Утверждается следующее: T>>
In this example, LazyGet is always guaranteed to return “42.” However, if the _box field were not volatile, LazyGet would be allowed to return “0” for two reasons: the reads could get reordered, or the writes could get reordered.
S>race condition, b уже не null, но еще не 42...
А как к этому могут привести переупорядочивание "Read 1" и "Read 2"?
Здравствуйте, mrTwister, Вы писали:
S>>race condition, b уже не null, но еще не 42...
T>А как к этому могут привести переупорядочивание "Read 1" и "Read 2"?
Здравствуйте, mrTwister, Вы писали:
T>C "writes" все понятно, но как в данном случае могут быть переупорядочены (reordered) "Read 1" и "Read 2" так,
Здесь автор статьи слегка ерунду написал.
Read 1 и Read 2 не могут быть переупорядочены, бо являются зависимыми. Чтобы выполнить Read 2, нужно сначала узнать адрес, который даёт Read 1.
Однако это вовсе не означает, что всё хорошо.
В пределах пути исполнения кода в потоке все команды не-volatile чтения по одному конкретному адресу могут быть элиминированы путём замены на какое-нибудь локальное кэширование — в регистре\на стеке\в потрохах контроллера памяти — результата самого первого такого чтения. Сия оптимизация корректна на всех отрезках пути исполнения кода между командами записи и\или volatile чтения по данному конкретному адресу.
И в исходном примере, если поток словил момент передачи недосконструированного\недоинициализированного объекта, то в общем случае возможна ситуация, когда все дальнейшие вызовы LazyGet() в этом потоке будут приводить к возвращению 0, несмотря на то, что в памяти значение уже давно поменялось.
T>чтобы вернулось 0, при том, что writes не переупорядочены?
Если путь исполнения кода в потоке не проходит через внутренности if, то Write'ов вообще нет.