在.NET中进行双重检查锁定需要什么样的"易失性"操作

Fei*_*Fei 8 .net c# multithreading

我从Joe Duffy的书"Windows上的并发编程"中获取了DCL的代码

class LazyInit<T> where T : class
{
private volatile T m_value;
private object m_sync = new object();
private Func<T> m_factory;
public  LazyInit(Func<T>  factory)  {  m_factory  =  factory;  }
public T value
{
  get
  {
    if (m_value == null)
    {
      lock (m_sync)
      {
        if (m_value == null)
        {
          m_value = m_factory();
        }
      }
    }
    return m_value;
  }
}
}
Run Code Online (Sandbox Code Playgroud)

据说标记m_value volatile可以防止写入重新排序,这将导致其他线程获得"具有未初始化字段的非空对象".如果问题恰好因为可能的写入重新排序而发生,我可以使用"易失性写入"而不是标记字段易失性,如下所示吗?(这个代码对于演示看起来有点尴尬,我只是想确定我们是否只能使用volatile write)

class LazyInit<T> where T : class
{
private object m_value;
private object m_sync = new object();
private Func<T> m_factory;
public  LazyInit(Func<T>  factory)  {  m_factory  =  factory;  }
public T value
{
  get
  {
    if (m_value == null)
    {
      lock (m_sync)
      {
        if (m_value == null)
        {
          Thread.VolatileWrite(ref m_value, m_factory());
        }
      }
    }
    return (T)m_value;
  }
}
}
Run Code Online (Sandbox Code Playgroud)

一个相关的问题是本书中的Interlocked版本

class LazylnitRelaxedRef<T> where T : class
{
private volatile T m_value;
private Func<T> m_factory;
public LazylnitRelaxedRef(Func<T> factory) { m_factory = factory; }
public T Value
{
  get
  {
    if (m_value == null)
      Interlocked.CompareExchange(ref  m_value, m_factory(), null);
    return m_value;
  }
}
}
Run Code Online (Sandbox Code Playgroud)

由于ECMA-CLI规定"互锁操作执行隐式获取/释放操作",在这种情况下我们是否还需要易失性?

Dan*_*Man 1

首先,搞乱 volatile 真的很难,所以不要太随意!但是,是对您的问题的一个非常接近的答案,我认为每个人都应该在使用关键字 , 之前阅读这篇文章volatile并且绝对在开始使用VolatileRead,VolatileWrite和 之前阅读MemoryBarrier

第一个链接中的答案是:不,您不需要使用 volatile,您只需要System.Threading.Thread.MemoryBarrier()在分配新值之前使用 RIGHT。这是因为release_fence使用 volatile 关键字时隐含的含义确保它已完成向主内存的写入,并且在完成之前无法执行读/写操作。

那么,Thread.VolatileWrite() 的作用是什么?它执行的功能是否与我们从 'volatile' 关键字获得的功能相同?好吧,这是该函数的完整代码:

public static void VolatileWrite (ref int address, int value)
{
  MemoryBarrier(); address = value;
}
Run Code Online (Sandbox Code Playgroud)

是的,它在分配您的值之前调用 MemoryBarrier,这就足够了!