C#中的Volatile和Thread.MemoryBarrier

为了实现multithreading应用程序的无锁代码 ,我使用了volatile变量, 理论上volatile关键字用于确保所有线程都能看到volatile变量的最新值; 因此,如果线程A更新变量值,并且线程B在更新发生之后读取该变量,它将看到最近从线程A写入的最新值。正如我在Nutshell书中的C#4.0中读到的那样,这是不正确的,因为

应用volatile不会阻止写入后读取交换。

可以通过在每次获取volatile变量之前放置Thread.MemoryBarrier()来解决这个问题:

 private volatile bool _foo = false; private void A() { //… Thread.MemoryBarrier(); if (_foo) { //do somthing } } private void B() { //… _foo = true; //… } 

如果这解决了问题; 考虑我们有一个while循环,它依赖于其中一个条件的值; 在while循环之前放置Thread.MemoryBarrier()是解决问题的正确方法吗? 例:

 private void A() { Thread.MemoryBarrier(); while (_someOtherConditions && _foo) { // do somthing. } } 

为了更准确,我希望_foo变量在任何时候任何线程都要求它时给出最新的值; 所以如果在调用变量之前插入Thread.MemoryBarrier()修复了问题,那么我可以使用Foo属性而不是_foo并在该属性的get中执行Thread.MemoryBarrier()

 Foo { get { Thread.MemoryBarrier(); return _foo; } set { _foo = value; } } 

“C#In a Nutshell”是正确的,但它的说法没有实际意义。 为什么?

  • 如果它在单个线程中影响逻辑那么无论如何,保证在程序顺序中发生’write’后跟’read’,没有’volatile’。
  • 在multithreading程序中“读取”之前的“写入”在您的示例中完全没有意义

让我们澄清一下。 拿你原来的代码:

 private void A() { //… if (_foo) { //do something } } 

如果线程调度程序已经检查了_foo变量会发生什么,但是在//do something注释之前它会被挂起? 好吧,那时你的另一个线程可以改变_foo的值,这意味着你所有的挥发物和Thread.MemoryBarriers都没有计算! 如果在_foo值为false的情况下避免使用do_something是绝对必要的,那么除了使用锁之外别无选择。

但是,如果在突然_foo变为false时执行do something操作是可以的,那么这意味着volatile关键字足以满足您的需求。

需要明确的是:所有告诉您使用记忆障碍的响应者都不正确或者提供过度杀伤力。

这本书是对的。
CLR的内存模型表明可以重新排序加载和存储操作。 这适用于易失性非易失性变量。

将变量声明为volatile仅意味着加载操作将具有获取语义,并且存储操作将具有释放语义。 此外,编译器将避免执行某些优化,这些优化继续以序列化的单线程方式访问变量(例如,从循环中提升加载/存储)。

单独使用volatile关键字不会创建关键部分,也不会导致线程彼此神奇地同步。

编写无锁代码时应该非常小心。 没有什么简单的事情,甚至专家都很难做到正确。
无论你想要解决的原始问题是什么,都可能有更合理的方法来实现它。

在你的第二个例子中,你还需要放一个Thread.MemoryBarrier(); 在循环内部,以确保每次检查循环条件时都获得最新值。

拉从这里 ……

 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将评估为真。

所以,如果我们回到你的循环示例……它应该是这样的……

 private void A() { Thread.MemoryBarrier(); while (_someOtherConditions && _foo) { //do somthing Thread.MemoryBarrier(); } } 

微软自己关于内存障碍的话:

MemoryBarrier仅在内存排序较弱的多处理器系统上需要(例如,采用多个Intel Itanium处理器的系统)。

在大多数情况下,C#lock语句,Visual Basic SyncLock语句或Monitor类提供了更简单的数据同步方法。