Search code examples
c#.netmultithreadingvolatile

Why doesn't C# volatile protect write-read reordering?


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?


Solution

  • 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.