I took the code of DCL from Joe Duffy’s book ‘Concurrent programming on windows’
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;
}
}
}
it is said marking m_value volatile can prevent writes reordering that will leads to other threads getting ‘non null object with uninitialized fields’. If the problem happens just because the possible writes reordering, can I just use ‘Volatile Write’ instead of marking the filed volatile, like below? (This code looks a little awkward for demonstration, I just want to make sure if we can only use volatile write instead)
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;
}
}
}
A related question is the Interlocked version from the book
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;
}
}
}
Since the ECMA-CLI specs the ‘Interlocked operation perform implicit acquire/release operations’, do we still need volatile in this case?
First, messing with volatile is really hard, so don’t get too loose with it! But, here is a really close answer to your question, and here is an article that I think everyone should read before using the keyword
volatile, and definitely before starting to useVolatileRead,VolatileWriteandMemoryBarrier.The answer in the first link is: no you don’t need to use volatile, you just need to use
System.Threading.Thread.MemoryBarrier()RIGHT BEFORE you assign the new value. This is because therelease_fenceimplied when using the volatile keyword makes sure that it gets finished writing out to the main memory, and that no read/write operations can be performed until it’s finished.So, what does Thread.VolatileWrite() do, and does it perform the same functions that we get from the ‘volatile’ keyword? Well, here’s the full code from this function:
Yes, it calls MemoryBarrier right before it assigns your value, which is sufficient!