Joe Albahari has a great series on multithreading that's a must read and should be known by heart for anyone doing C# multithreading.
In part 4 however he mentions the problems with volatile:
Notice that applying volatile doesn’t prevent a write followed by a read from being swapped, and this can create brainteasers. Joe Duffy illustrates the problem well with the following example: if Test1 and Test2 run simultaneously on different threads, it’s possible for a and b to both end up with a value of 0 (despite the use of volatile on both x and y)
Followed by a note that the MSDN documentation is incorrect:
The MSDN documentation states that use of the volatile keyword ensures that the most up-to-date value is present in the field at all times. This is incorrect, since as we’ve seen, a write followed by a read can be reordered.
I've checked the MSDN documentation, which was last changed in 2015 but still lists:
The volatile keyword indicates that a field might be modified by multiple threads that are executing at the same time. Fields that are declared volatile are not subject to compiler optimizations that assume access by a single thread. This ensures that the most up-to-date value is present in the field at all times.
Right now I still avoid volatile in favor of the more verbose to prevent threads using stale data:
private int foo;
private object fooLock = new object();
public int Foo {
get { lock(fooLock) return foo; }
set { lock(fooLock) foo = value; }
}
As the parts about multithreading were written in 2011, is the argument still valid today? Should volatile still be avoided at all costs in favor of locks or full memory fences to prevent introducing very hard to produce bugs that as mentioned are even dependent on the CPU vendor it's running on?
return
and assignment statement bylock
in your example? – Detonatorwhile(!Stopped)
for background service work on a separate thread. If the windows service stops, I put Stopped to true so the separate thread jumps out of the loop. Without locking (and thus fencing) the backing field of the property might remain cached in the CPU, will never be refreshed and the thread will never stop. – Dahtry ... finally
) – Dahvolatile
does, and in any case can't prevent many classes of synchronization problems. Most of these relate to the interleaving of two threads - for instance, attempting to do something likefoo++
from two threads simultaneously can still have undefined results, and why should be more obvious given your locking example: there's a separate lock for each the load and store, meaning two threads have to fight over who goes first for each operation (whether this is relevant to your current code using this is unknown at this time). – Paraphrastic