【发布时间】:2011-03-30 11:10:56
【问题描述】:
C# 4 in a Nutshell(强烈推荐顺便说一句)使用以下代码来演示 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!我的一部分说这是因为线程 2(运行 B)可能已经运行到(但不包括)if(_complete),所以我们需要确保 _complete 被刷新.
但是,我看不出这有什么帮助。 _complete 是否仍然有可能在 A 中设置为 true,但 B 方法会看到 _complete 的缓存(错误)版本?即,如果线程 2 运行方法 B 直到第一个 MemoryBarrier 之后,然后线程 1 运行方法 A 直到 _complete = true 但没有进一步,然后线程 1 恢复并测试 if(_complete) -- if 会不会导致 false?
【问题讨论】:
-
@Chaos:CLR via C# book (Richter) 有一个很好的解释 - IIRC 是'volatile' 意味着对 var 的所有访问都被视为 volatile 并在两个方向上强制执行完整的内存屏障。如果您只需要读取或写入屏障并且仅在特定访问中,那么这通常比必要的性能更高。
-
@Chaos:不是重点,但一个原因是 volatile 在编译器优化方面有其自己的怪癖,可能会导致死锁,请参阅 bluebytesoftware.com/blog/2009/02/24/…
-
@statichippo:说真的,如果你正在处理这种代码(不仅仅是学习它),请阅读 Richter 的书,我再怎么推荐也不为过。 amazon.com/CLR-via-Dev-Pro-Jeffrey-Richter/dp/0735627045
-
@James:volatile 关键字强制执行“半”屏障(加载-获取 + 存储-释放)——而不是完整的屏障。如果你引用里希特的话,那么他在这一点上是错误的。 Joe Duffy 的“Windows 中的并发编程”中有很好的解释。
-
我开始怀疑是否有人写过一段代码,需要没有错误的 MemoryBarriers。
标签: c# multithreading thread-safety shared-memory memory-barriers