4

Possible Duplicate:
Volatile vs. Interlocked vs. lock

I am trying to understand situations when I would want to use the volatile keyword, vs Interlocked.

If I have a variable where every read from, and write to, that object is via Interlocked.Exchange, is that essentially the same thing as marking that variable as volatile?

private object _threadSafe;
private void Test()
{
    var tmp = new object();
    Interlocked.Exchange(ref tmp, _threadSafe); //read
    Interlocked.Exchange(ref _threadSafe, "hi"); //write

}

If, instead of this, the _threadSafe object was marked as volatile and I removed the Interlocked's, would that be functionally equivalent? (Assume I am not dependent on atomic read/increment/writes, like incrementing a value).

Community
  • 1
  • 1
user981225
  • 8,762
  • 6
  • 32
  • 41

2 Answers2

4

volatile guarantees that accesses are visible to other cores, as does Interlocked. The difference with interlocked is that it uses a full memory barrier for its guarantees and handles non-atomic operations. Volatile may not use a full memory barrier (depends on platform, e.g. x86/x64 don't need a full memory barrier with volatile...) but only makes atomic operations "thread-safe".

It's generally recommended to avoid volatile because it makes every atomic access to that variable "volatile" (which may not be that big a deal on x86/x64) and kinda hides the fact that access to the variable is different. Something like Interlocked is generally more recommended because it explicitly details the thread-safety concerns on each use of the variable.

Also, you can't use volatile on local variables, so if you wanted to use local variables with multiple threads, then Interlocked may be necessary. For example:

static void Main()
{
  bool complete = false; 
  var t = new Thread (() =>
  {
    bool toggle = false;
    while (!complete) toggle = !toggle;
  });
  t.Start();
  Thread.Sleep (1000);
  complete = true;
  t.Join();        // Blocks indefinitely
}

Update: To be clear, by "accesses" I mean already-atomic accesses. It should be blatantly obvious that just because a variable is "volatile" doesn't make every single operation on it thread-safe. That's not what I'm saying. e.g. on some platforms x++ is not thread-safe despite using volatile.

Peter Ritchie
  • 35,463
  • 9
  • 80
  • 98
  • This is simply incorrect. an increment is not an atomic operation; it involves reading and then writing. `Interlocked.Increment` does the appropriate locking to ensure that the operation is atomic, volatile does not. – Servy Oct 11 '12 at 16:03
  • 2
    I never said an increment was a single operation and the OP never mentioned increment. – Peter Ritchie Oct 11 '12 at 16:04
  • Every single one of the `Interlocked` methods represent some operation that isn't normally atomic (and wouldn't be atomic if using `volatile`), but which is atomic when using `Interlocked`. Increment is just a good example. – Servy Oct 11 '12 at 16:06
  • @Servy volatile can be used to make *some* operations thread-safe by forcing the compiler not to optimize member access into a register that can't be seen by other cores and to use acquire/release semantics on platforms with loose memory models. Does that make *every* situation it's used in thread-safe? Of course not, nothing does that. Of course I'm not saying volatile makes everything thread-safe. But, it does, by definition make certain, already atomic, operations thread safe. – Peter Ritchie Oct 11 '12 at 16:10
  • `Volatile` introduces a memory barrier before reading any variable it marks. It applies no additional synchronization. All of the `Interlocked` methods apply a memory barrier, in addition to using locking methods to ensure that a transaction of several operations which normally wouldn't be atomic, are atomic. If you only want to perform a single read of a variable from several threads, knowing that you are only modifying it from one, then you could use either. If you are reading and writing from several threads `volatile` won't help, and `Interlocked` will work fine. They aren't "the same". – Servy Oct 11 '12 at 16:15
  • All of the `Interlocked` methods essentially do what `volatile` would do *and then some*. You can always use `Interlocked` instead of volatile, but you often can't use `volatile` instead of `Interlocked`. – Servy Oct 11 '12 at 16:16
  • `volaltile` *does not* introduce a memory barrier before reading any variable is marks on all platforms. – Peter Ritchie Oct 11 '12 at 16:18
  • It ensures that that one variable's memory is in sync; it doesn't (need to) ensure that *all* memory is in sync. The point remains that `Interlocked` does what `volatile` does *and then some*. If you only need the protection given by `volatile` then it may be more efficient, but `Interlocked` can do many more things atomically than `volatile` can. Saying that they're effectively the same simply isn't true, regardless of the specific implementation details of each operation. – Servy Oct 11 '12 at 16:22
2

As others have stated, volatile doesn't allow you to perform many of the operations that Interlocked safely allows to be done atomically. Some code will actually be safe, and not have race conditions, despite no synchronization methods at all. For example, if there is a single thread writing to an integer and N different threads only reading from it, then you may not have any problems and all and not need any locking on that integer. There is no race condition where you check a variable, add one to it, and then have someone else set it before you can set your result (thus resulting in you essentially overwriting their write).

The problem here is that due to compiler optimizations, thread/processor specific caches, etc. it's possible for those N other threads to not see the updates of the one writing thread. The memory won't be "in sync". They'll each be reading/writing entirely different variables and so changes won't be seen by other threads. You need to add specific memory barriers in which all of the various representations of the same variable are synchronized for the code to work. When you use lock or other synchronization methods C# automatically knows you also need to have a memory barrier, so you don't need t tell it anything. When using lock-less synchronization there is nothing to introduce this barrier, so that's what volatile is for.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • How is `[volatile] restrictions ensure that all threads will observe volatile writes performed by any other thread in the order in which they were performed.` doing nothing to ensure thread safety? – Peter Ritchie Oct 11 '12 at 15:34
  • @PeterRitchie Well, I never said that, and that's because it's not true. `Volatile` doesn't sure that order is preserved (just that actual writes are actually seen at all from all thread), which makes that "quote" false, because `volatile` is not thread safe. – Servy Oct 11 '12 at 16:04
  • that is a quote from the C# spec... – Peter Ritchie Oct 11 '12 at 16:06
  • @PeterRitchie Then you should quote it and put it in context. Also see [this link](http://blogs.msdn.com/b/ericlippert/archive/2009/10/19/what-is-this-thing-you-call-thread-safe.aspx) with reference to "thread safety". Volatile ensures that any single read or write is "thread safe" but not that multiple read/writes are thread safe (they aren't). `Interlocked` ensures that a set of reads/writes are made "thread safe". – Servy Oct 11 '12 at 16:09
  • "Volatile ensures that any single read or write is 'thread safe'" is not the same as "volatile doesn't do anything to ensure thread safety". – Peter Ritchie Oct 11 '12 at 16:13
  • It seems you didn't get past the first sentence in my answer. I agree it is vague and could be interpreted differently (so I have re-written it) but I feel that the remainder of the answer clarifies what I meant and explains the true difference between `volatile` and other synchronization techniques. – Servy Oct 11 '12 at 16:26