According to this online book, the volatile
keyword in C# does not protect against reordering Write operations followed by Read operations. It gives this example in which both a
and b
can end up being set to 0
, despite x
and y
being volatile
:
class IfYouThinkYouUnderstandVolatile
{
volatile int x, y;
void Test1() // Executed on one thread
{
x = 1; // Volatile write (release-fence)
int a = y; // Volatile read (acquire-fence)
...
}
void Test2() // Executed on another thread
{
y = 1; // Volatile write (release-fence)
int b = x; // Volatile read (acquire-fence)
...
}
}
This seems to fit with what the specification says in 10.5.3:
A read of a volatile field is called a volatile read. A volatile read has “acquire semantics”; that is, it is guaranteed to occur prior to any references to memory that occur after it in the instruction sequence.
A write of a volatile field is called a volatile write. A volatile write has “release semantics”; that is, it is guaranteed to happen after any memory references prior to the write instruction in the instruction sequence.
What is the reason for this? Is there a use case in which we don't mind Write-Read operations being reordered?
Volatile does not guarantee reads and writes of independent volatile variables are not re-ordered, it only guarantees that reads get the most up-to-date value (non-cached). (reads and writes to a single variable are guaranteed to maintain order)
http://msdn.microsoft.com/en-us/library/x13ttww7%28v=vs.71%29.aspx
The system always reads the current value of a volatile object at the point it is requested, even if the previous instruction asked for a value from the same object. Also, the value of the object is written immediately on assignment.
The volatile modifier is usually used for a field that is accessed by multiple threads without using the lock statement to serialize access. Using the volatile modifier ensures that one thread retrieves the most up-to-date value written by another thread.
Whenever you have multiple dependent operations, you need to use some other synchronization mechanism. Usually use lock
, it's easiest and only creates performance bottlenecks when abused or in very extreme situations.