易失性字段:如何实际获取字段的最新写入值?

Xen*_*ate 7 .net c# multithreading volatile .net-4.5

考虑以下示例:

private int sharedState = 0;

private void FirstThread() {
    Volatile.Write(ref sharedState, 1);
}

private void SecondThread() {
    int sharedStateSnapshot = Volatile.Read(ref sharedState);
    Console.WriteLine(sharedStateSnapshot);
}
Run Code Online (Sandbox Code Playgroud)

直到最近,我的印象是,只要FirstThread()真的执行过SecondThread(),这个程序除了1之外不能输出任何东西.

但是,我现在的理解是:

  • Volatile.Write()发出一个发布栏.这意味着在分配1到之后,可能不会发生先前的加载或存储(按程序顺序).sharedState
  • Volatile.Read()发出一个获取围栏.这意味着,没有后续的加载或存储(按程序顺序)可以的复制之前发生sharedStatesharedStateSnapshot.

或者,换句话说:

  • sharedState实际发布到所有处理器核心时,该写入之前的所有内容也将被释放,并且,
  • sharedStateSnapshot获取地址中的值时; sharedState一定是已经被收购了.

如果我的理解是正确的,那么sharedState如果写入FirstThread()还没有被释放,那么没有什么可以防止获得"陈旧" .

如果这是真的,我们如何才能真正确保(假设最弱的处理器内存模型,如ARM或Alpha),程序将始终打印1?(或者我在某个地方的心理模型中犯了错误?)

Kri*_*ten 6

您的理解是正确的,并且您无法确保程序始终使用这些技术打印1.为了确保程序打印1,假设线程2在第一个线程之后运行,则每个线程需要两个栅栏.

实现这一目标的最简单方法是使用lock关键字:

private int sharedState = 0;
private readonly object locker = new object();

private void FirstThread() 
{
    lock (locker)
    {
        sharedState = 1;
    }
}

private void SecondThread() 
{
    int sharedStateSnapshot;
    lock (locker)
    {
        sharedStateSnapshot = sharedState;
    }
    Console.WriteLine(sharedStateSnapshot);
}
Run Code Online (Sandbox Code Playgroud)

我想引用Eric Lippert的话:

坦率地说,我不鼓励你做一个不稳定的领域.易失性字段表明你正在做一些彻头彻尾的疯狂:你试图在两个不同的线程上读取和写入相同的值,而不是锁定到位.

这同样适用于呼叫Volatile.ReadVolatile.Write.实际上,它们甚至比volatile字段更糟糕,因为它们要求您手动执行volatile修改器自动执行的操作.