С# 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 будет оцениваться как правда.
Я не понимаю. Я думаю, что я понимаю, почему нужны барьеры 1 и 4: мы не хотим, чтобы запись _answer была оптимизирована и помещена после записи в _complete (Barrier 1) и мы должны убедиться, что _answer не кэшируется (Barrier 4). Я также думаю, что я понимаю, почему Барьер 3 необходим: если A пробежал до тех пор, пока не напишет _complete = true, B все равно нужно обновить _complete, чтобы прочитать правильное значение.
Я не понимаю, почему нам нужен барьер 2! Часть меня говорит, что это потому, что Thread 2 (работа B) уже выполнялся до (но не включая) if (_complete), и поэтому нам нужно убедиться, что _complete обновлен,
Однако я не вижу, как это помогает. Разве не возможно, что _complete будет установлено значение true в A, но все же метод B увидит кэшированную (ложную) версию _complete? То есть, если Thread 2 запускал метод B до первого MemoryBarrier, а затем Thread 1 запускал метод A до _complete = true, но не более того, а затем Thread 1 возобновил и протестировал if (_complete) - может ли , если не соответствовать false?