我从 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;
}
}
}
据说将 m_value 标记为 volatile 可以防止写入重新排序,这将导致其他线程获得“具有未初始化字段的非空对象”。如果问题只是因为可能的写入重新排序而发生,我是否可以只使用“ volatile 写入”而不是将文件标记为 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;
}
}
}
一个相关的问题是书中的 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;
}
}
}
既然 ECMA-CLI 规范了“Interlocked operation perform implicit acquire/release operations”,在这种情况下我们还需要 volatile 吗?
最佳答案
首先,搞乱 volatile 真的很难,所以不要太松懈!但是,here是对您问题的非常接近的答案,here这篇文章我认为每个人都应该在使用关键字volatile
之前阅读,并且绝对在开始使用VolatileRead
、VolatileWrite
和MemoryBarrier之前阅读
。
第一个链接中的答案是:不,您不需要使用 volatile,您只需要在分配新值之前使用 System.Threading.Thread.MemoryBarrier()
。这是因为使用 volatile 关键字时隐含的 release_fence
确保它完成写入主内存,并且在完成之前不能执行读/写操作。
那么,Thread.VolatileWrite() 做了什么,它执行的功能是否与我们从“volatile”关键字获得的功能相同?那么,这是此函数的完整代码:
public static void VolatileWrite (ref int address, int value)
{
MemoryBarrier(); address = value;
}
是的,它会在分配您的值之前调用 MemoryBarrier,这就足够了!
关于c# - .NET 中的双重检查锁定需要什么样的 'volatile' 操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8279575/