C#中的Volatile和Thread.MemoryBarrier

Jal*_*aid 9 c# memory-management volatile nonblocking memory-barriers

为了实现多线程应用程序的无锁代码,我使用了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;
    //…
}
Run Code Online (Sandbox Code Playgroud)

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

private void A()
{
    Thread.MemoryBarrier();
    while (_someOtherConditions && _foo)
    {
        // do somthing.
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Foo
{
    get 
    {
        Thread.MemoryBarrier();
        return _foo;
    }
    set
    {
        _foo = value;
    }
}
Run Code Online (Sandbox Code Playgroud)

Bre*_*ias 9

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

  • 如果在单个线程中影响逻辑,那么无论如何,"写入"后跟"读取","非易失性",保证会以程序顺序发生.
  • 在多线程程序中"读取"之前的"写入" 在您的示例中完全没有意义.

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

private void A() 
{ 
    //… 
    if (_foo) 
    { 
        //do something 
    } 
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • @Jalal:`volatile`声明已经是gaurantees,它本身就是变量不会存储在CPU寄存器中.它可以直接进行内存读取和写入(使多处理器计算机上其他处理器的缓存无效).因此,显性记忆障碍是不必要的; `volatile`已经在完成你所需要的. (2认同)

Lir*_*ran 5

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

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

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

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

  • 如果操作之间没有数据依赖性(例如,加载到X,并存储到Y),则可以重新排序存储操作@Jalal,然后执行读取操作.确保您了解CLR的内存模型.Joe Duffy在总结规则方面做得很好:http://www.bluebytesoftware.com/blog/2007/11/10/CLR20MemoryModel.aspx ...此外,您使用属性的事实与您的代码无关线程安全. (3认同)