12

为了实现多线程应用程序的无锁代码,我使用了volatile变量, 理论上:该volatile关键字仅用于确保所有线程都能看到 volatile 变量的最新值;因此,如果线程A更新变量值并且线程B在更新发生后立即读取该变量,它将看到最近从线程 A 写入的最新值。正如我在Nutshell书中的 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执行 a就像:Thread.MemoryBarrier()

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

5 回答 5

11

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

  • 如果它影响单个线程中的逻辑,则“写入”后跟“读取”,没有“易失性”,无论如何都保证以程序顺序发生
  • 在您的示例中,多线程程序中“读取”之前的“写入”完全没有意义。

让我们澄清一下。获取您的原始代码:

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

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

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

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

于 2010-11-24T19:19:01.923 回答
5

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

将变量声明为volatileonly 意味着加载操作将具有获取语义,而存储操作将具有释放语义。此外,编译器将避免执行某些优化,这些优化依赖于以序列化、单线程方式访问变量的事实(例如,将加载/存储提升到循环之外)。

单独使用volatile关键字不会创建临界区,也不会导致线程神奇地相互同步。

编写无锁代码时应格外小心。没有什么简单的,即使是专家也很难把它做好。
无论您要解决的原始问题是什么,都可能有更合理的方法来解决。

于 2010-11-24T18:52:41.003 回答
1

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

于 2010-11-24T17:24:48.203 回答
1

这里拉...

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();
    }
}
于 2010-11-24T17:32:16.277 回答
0

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

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

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

于 2010-11-24T18:18:11.943 回答