C # 4 в двух словах (настоятельно рекомендуется кстати) использует следующий код для демонстрации концепции MemoryBarrier (предполагая, что A и B выполнялись в разных потоках):
class Foo{
int _answer;
bool complete;
void A(){
_answer = 123;
Thread.MemoryBarrier(); // Barrier 1
_complete = true;
Thread.MemoryBarrier(); // Barrier 2
}
void B(){
Thread.MemoryBarrier(); // Barrier 3;
if(_complete){
Thread.MemoryBarrier(); // Barrier 4;
Console.WriteLine(_answer);
}
}
}
они упоминают, что барьеры 1 и 4 предотвращают этот пример из записи 0 и барьеров 2 и 3 обеспечивает гарантию свежести : они гарантируют, что если B бежит за A, чтение _complete даст оценку true .
Я не совсем понял. Я думаю, я понимаю, почему барьеры 1 и 4 необходимы: мы не хотим, чтобы запись в _answer была оптимизирована и помещена после записи в _complete (барьер 1), и нам нужно чтобы убедиться, что _answer не кэшируется (Барьер 4). Я также думаю, что понимаю, почему необходим барьер 3: если A запускается только после записи _complete = true , B все равно потребуется обновить _complete , чтобы прочитать правильное значение.
Я не понимаю, зачем нам Барьер 2! Часть меня говорит, что это s потому что, возможно, поток 2 (запущенный B) уже выполнялся до (но не включая) if (_complete) , и поэтому нам нужно убедиться, что _complete обновлено.
Однако я не понимаю, как это помогает. Возможно ли, что для _complete будет установлено значение true в A, но метод B увидит кэшированную (ложную) версию _complete ? То есть, если поток 2 запускал метод B до тех пор, пока после первого MemoryBarrier, а затем поток 1 не выполнял метод A до _complete = true , но не дальше, а затем поток 1 возобновил и протестировал if (_complete) - может ли это , если не приведет к ложному ?
Барьер №2 гарантирует немедленную фиксацию записи в _complete
. В противном случае он может оставаться в состоянии очереди, что означает, что чтение _complete
в B
не увидит изменения, вызванного A
, даже если B
] эффективно использовал изменчивое чтение.
Конечно, этот пример не совсем отражает проблему, потому что A
больше ничего не делает после записи в _complete
, что означает, что запись в любом случае будет завершена немедленно, поскольку поток заканчивается рано.
Ответ на ваш вопрос о том, может ли if
по-прежнему оцениваться как false
, да по точно указанным вами причинам. Но обратите внимание на то, что автор говорит по этому поводу.
Этому примеру препятствуют барьеры 1 и 4. от написания «0».Барьеры 2 и 3 дают гарантию свежести: они убедитесь, что , если B идет после A , чтение _complete будет иметь значение true.
Акцент на том, что «если B побежит после A», принадлежит мне. Конечно, может случиться так, что два потока чередуются. Но автор проигнорировал этот сценарий, по-видимому, чтобы указать на то, как Thread.MemoryBarrier
работает проще.
Между прочим, мне было нелегко придумать пример на моей машине, где барьеры №1 и №2 изменили бы поведение программы. Это потому, что в моей среде была сильна модель памяти относительно записи. Возможно, если бы у меня была многопроцессорная машина, я бы использовал Mono или какую-то другую настройку, я мог бы это продемонстрировать. Конечно, было легко продемонстрировать, что снятие барьеров №3 и №4 оказало влияние.