C#中的Volatile和Thread.MemoryBarrier

时间:2010-11-24 17:09:07

标签: c# memory-management nonblocking volatile memory-barriers

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

  

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

可以通过在每次获取Thread.MemoryBarrier()变量之前添加volatile来解决此问题,如:

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;
    }
}

5 个答案:

答案 0 :(得分:9)

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

  • 如果在单个线程中影响逻辑,则无论如何保证在程序顺序中发生'read'后跟'read',并且'volatile',
  • 在多线程程序中“读取”之前的“写入”完全没有意义在您的示例中担心。

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

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

如果线程调度程序已经检查了_foo变量,但在//do something注释之前它被暂停,会发生什么?好吧,那时你的另一个线程可能会改变_foo的值,这意味着你所有的挥发性物品和Thread.MemoryBarriers都没有计算!如果do_something的值为false,则绝对必须避免_foo,那么您别无选择,只能使用锁定。

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

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

答案 1 :(得分:5)

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

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

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

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

答案 2 :(得分:0)

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

答案 3 :(得分:0)

here ...

拉出来
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。

因此,如果我们回到您的循环示例......这就是它应该看起来的样子......

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

答案 4 :(得分:0)

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

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

对于大多数用途,C#lock语句,Visual Basic SyncLock语句或Monitor类提供了更简单的数据同步方法。