Nutshell中的C#4(强烈推荐的btw)使用以下代码来演示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&amp; 4是必要的:我们不希望在写入 _complete (障碍1)之后优化并放置写入 _answer ,我们需要确保 _answer 未缓存(障碍4)。我也认为我理解为什么Barrier 3是必要的:如果A在写完 _complete = true 之后才运行,B仍然需要刷新 _complete 来读取正确的值。< / p>
我不明白为什么我们需要障碍2!我的一部分说这是因为也许线程2(运行B)已经运行直到(但不包括) if(_complete),因此我们需要确保刷新 _complete 。
但是,我不知道这有多大帮助。是否仍然可以在A中将 _complete 设置为true,但是B方法会看到 _complete 的缓存(错误)版本?即,如果线程2运行方法B直到第一个MemoryBarrier之后,然后线程1运行方法A直到 _complete = true 但没有进一步,然后线程1恢复并测试 if(_complete)< / strong> - 如果不能导致错误?
答案 0 :(得分:26)
屏障#2保证对_complete
的写入立即生效。否则,它可能会保持排队状态,这意味着即使_complete
有效地使用了易失性读取,B
中A
的读取也看不到由B
引起的更改。
当然,这个例子并不能完全解决这个问题,因为A
在写入_complete
之后不再做任何事情,这意味着自从线程提前终止以来,无论如何都会立即进行写入。 / p>
关于if
仍然可以评估为false
的问题的答案是肯定的,原因正如您所述。但是,请注意作者关于这一点的说法。
障碍1和4阻止了这个例子 写“0”。障碍2和3 提供新鲜度保证:他们 确保如果B在A 之后运行,则读取 _complete将评估为true。
强调“如果B跑过A”是我的。当然可能是两个线程交错的情况。但是,作者忽略了这种情况,大概是为了说明Thread.MemoryBarrier
如何更简单地发挥作用。
顺便说一句,我很难在我的机器上设计一个示例,其中障碍#1和#2会改变程序的行为。这是因为关于写入的内存模型在我的环境中很强。也许,如果我有一台多处理器机器,使用Mono,或者有一些其他不同的设置我可以证明它。当然,很容易证明消除障碍#3和#4会产生影响。
答案 1 :(得分:2)
这个例子不清楚有两个原因:
如果您考虑以下因素,则会更清楚:
聚苯乙烯。 This article很好地解释了x86的内部工作原理。