Do I need this field to be volatile?

2019-06-16 15:26发布

问题:

I have a thread that spins until an int changed by another thread is a certain value.

int cur = this.m_cur;
while (cur > this.Max)
{
    // spin until cur is <= max
    cur = this.m_cur; 
}

Does this.m_cur need to be declared volatile for this to work? Is it possible that this will spin forever due to compiler optimization?

回答1:

Yes, that's a hard requirement. The just-in-time compiler is allowed to store the value of m_cur in a processor register without refreshing it from memory. The x86 jitter in fact does, the x64 jitter doesn't (at least the last time I looked at it).

The volatile keyword is required to suppress this optimization.

Volatile means something entirely different on Itanium cores, a processor with a weak memory model. Unfortunately that's what made it into the MSDN library and C# Language Specification. What it is going to to mean on an ARM core remains to be seen.



回答2:

The blog below has some fascinating detail on the memory model in c#. In short, it seems safer to use the volatile keyword.

http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/

From the blog below

class Test
{
    private bool _loop = true;

    public static void Main()
    {
        Test test1 = new Test();

        // Set _loop to false on another thread
        new Thread(() => { test1._loop = false;}).Start();

        // Poll the _loop field until it is set to false
        while (test1._loop == true) ;

        // The loop above will never terminate!
    }
}

There are two possible ways to get the while loop to terminate: Use a lock to protect all accesses (reads and writes) to the _loop field Mark the _loop field as volatile There are two reasons why a read of a non-volatile field may observe a stale value: compiler optimizations and processor optimizations.



回答3:

It depends on how m_cur is being modified. If it's using a normal assignment statement such as m_cur--;, then it does need to be volatile. However, if it's being modified using one of the Interlocked operations, then it doesn't because Interlocked's methods automatically insert a memory barrier to ensure that all threads get the memo.

In general, using Interlocked to modify atomic valued that are shared across threads is the preferable option. Not only does it take care of the memory barrier for you, but it also tends to be a bit faster than other synchronization options.

That said, like others have said polling loops are enormously wasteful. It would be better to pause the thread that needs to wait, and let whoever is modifying m_cur take charge of waking it up when the time comes. Both Monitor.Wait() and Monitor.Pulse() and AutoResetEvent might be well-suited to the task, depending on your specific needs.