18

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?

Community
  • 1
  • 1
Eric
  • 5,842
  • 7
  • 42
  • 71
  • my guess 1 thread is being started/scheduled slightly behind the other. – Daniel A. White Jul 31 '12 at 18:59
  • 1
    C# in a nutshell gives some information on this. Have to look up or Skeet is coming in here ;) – Mare Infinitus Jul 31 '12 at 19:00
  • 1
    why do you think that the volatile keyword should synchronize threads? I thought volatile only prevented the compiler from optimizing away memory access to that variable. – huysentruitw Jul 31 '12 at 19:02
  • If you need the guarantee, you can always use a `lock`. – Robert Harvey Jul 31 '12 at 19:04
  • @WouterH, I don't think it would synchronize threads - but it's easy to imagine that it would prevent reordering of operations involving `x` and `y`. And in the example above, the only way both `a` and `b` could end up as `0` is if reordering was occurring. – Eric Jul 31 '12 at 19:04
  • WouterH: synchronized or not, it doesn't follow that if one thread runs, and then the other, neither one of `a` or `b` will be 1. – Wug Jul 31 '12 at 19:05
  • @RobertHarvey, yeah I intend to use a `lock` in practice (because of this), but I'm curious as to why `volatile` doesn't prevent this type of reordering. – Eric Jul 31 '12 at 19:06
  • using `lock` `this` is an academic example of how to end up in deadlocks! be aware of that! – Mare Infinitus Jul 31 '12 at 19:07
  • @MareInfinitus: it will never deadlock if you simply put a lock around these operations in each thread. One thread will just have to wait. In fact, these kind of operations NEED a lock :) – huysentruitw Jul 31 '12 at 19:08
  • Albahari has a way of making these things sound simple: http://www.albahari.com/threading/part4.aspx#_The_volatile_keyword. – Robert Harvey Jul 31 '12 at 19:10
  • 2
    Also http://blogs.msdn.com/b/ericlippert/archive/2011/06/16/atomicity-volatility-and-immutability-are-different-part-three.aspx – Robert Harvey Jul 31 '12 at 19:12
  • 3
    And http://www.bluebytesoftware.com/blog/2010/12/04/SayonaraVolatile.aspx – Robert Harvey Jul 31 '12 at 19:13

3 Answers3

7

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.

Samuel Neff
  • 73,278
  • 17
  • 138
  • 182
  • 2
    "Volatile does not guarantee reads and writes are not re-ordered, it only guarantees that reads get the most up-to-date value (non-cached)." I don't think that's right. From the spec: "all threads will observe volatile writes performed by any other thread in the order they were performed" (taken from https://stackoverflow.com/q/10589565/ ) – Max Barraclough May 17 '18 at 15:02
  • @MaxBarraclough Perhaps I should be clearer. Yes reads/writes of the same volatile variable are guaranteed sequential order. Independent read/write of two unrelated variables (i.e., the expressions don't count on each other as in OP example) don't have the same guarantee. – Samuel Neff Jun 22 '21 at 14:23
  • This is exactly why `lock`` should be used whenever code is referencing multiple shared values (which due to lock do not need to be volatile). – Samuel Neff Jun 22 '21 at 14:25
4

Preventing volatile write with volatile read reordering will be quite expensive on x86/x64 arch. That's because of the write optimization called store buffers. Java went this way and volatile writes in Java are effectively full memory barriers at CPU instructions level.

OmariO
  • 506
  • 4
  • 11
3

Maybe too late to answer ... but I was looking around and saw this. Volatile-read is actually a call to a method similar to the following:

public static int VolatileRead(ref int address)
{
    int num = address;
    Thread.MemoryBarrier();
    return num;
}

And Volatile-write is like this:

public static int VolatileWrite(ref int address, int value)
{
    Thread.MemoryBarrier();
    adrdress = value;
}

The instruction MemoryBarrier(); is the one preventing from reordering.MemoryBarrier(); ensures that intructions before are executed before intructions after. When VW then VR, you will have:

Thread.MemoryBarrier();
adrdress = value; //this line may be reordered with the one bellow
int num = address;//this line may be reordered with the one above
Thread.MemoryBarrier();
return num;
adPartage
  • 829
  • 7
  • 12
  • 3
    As your answer illustrates, the answer to this question is actually quite simple: if anyone need sequentially consistency, one can always insert additional `Thread.MemoryBarrier()` where required. – rwong May 14 '16 at 06:45