10

I got confused on volatile for reference type .

I understand that for primitive type, volatile can reflect value changes from another thread immediately. For reference type, it can reflect the address changes immediately. However, what about the content of the object. Are they still cached?

(Assuming List.Add() is an atomic operation)

For example, I have:

class A
{
     volatile List<String> list;
     void AddValue()
     {
        list.Add("a value");
     }

}

If one thread calls the function AddValue, the address of list does not change, will another thread get updated about the "content" change of the list, or the content may be cached for each thread and it doesn't update for other threads?

Andrew Barber
  • 39,603
  • 20
  • 94
  • 123
Frank
  • 7,235
  • 9
  • 46
  • 56
  • I'm not to sure about this, but my guess is that given that list is actually just a pointer (address), when content gets updated it's immediately reflected everywhere, as the content would reside in the heap, however, with address-change the stack is changed, and I think that can reside on a single core. But, as said, no expert. It's only a guess. – Alxandr Dec 28 '11 at 23:30
  • Also, as a side-note. volatile turns of a lot of optimizations. A LOT of optimizations. It's strongly recommended by some to not use volatile modifiers, but rather use volatileread and volatilewrite when needed (some global static method found on a class in the system namespace, can probably be found on google). – Alxandr Dec 28 '11 at 23:33
  • 1
    @Alxandr: Be careful. Volatile read and volatile write are potentially expensive methods to call and they induce *full fences*, not the half fences one would expect. If you're writing lock-free code for performance reasons (and what other reason would you have to do this crazy thing?) it is entirely possible that VolatileRead and VolatileWrite are worse. You've got to profile it carefully. – Eric Lippert Dec 28 '11 at 23:57
  • volatile modifier induces volatile read and write on all, yes ALL, reads and writes to the field, but using volatile read and write is optional. Also, afaik, volatile read and write is ALWAYS, by a magnitude of at least 10, faster than locking, and it does not in any way do the same thing. At least that's what my video on threading in .NET told me (found on lidnug I think). – Alxandr Dec 29 '11 at 08:36
  • 1
    Now go and find a video that explains who you are disagreeing with/arguing with... :) – Benjamin Podszun Dec 29 '11 at 14:05

4 Answers4

21

I understand that for primitive type, volatile can reflect value changes from another thread immediately

You understand incorrectly in at least three ways. You should not attempt to use volatile until you deeply understand everything about weak memory models, acquire and release semantics, and how they affect your program.

First off, be clear that volatile affects variables, not values.

Second, volatile does not affect variables that contain values of value types any differently than it affects variables that contain references.

Third, volatile does not mean that value changes from other threads are visible immediately. Volatile means that variables have acquire and release semantics. Volatile affects the order in which side effects of memory mutations can be observed to happen from a particular thread. The idea that there exists a consistent universal order of mutations and that those mutations in that order can be observed instantaneously from all threads is not a guarantee made by the memory model.

However, what about the content of the object?

What about it? The storage location referred to by a volatile variable of reference type need not have any particular threading characteristics.

If one thread calls the function AddValue, the address of list does not change, will another thread get updated about the "content" change of the list.

Nope. Why would it? That other thread might be on a different processor, and that processor cache might have pre-loaded the page that contains the address of the array that is backing the list. Mutating the list might have changed the storage location that contains the address of the array to refer to some completely different location.

Of course, the list class is not threadsafe in the first place. If you're not locking access to the list then the list can simply crash and die when you try to do this.

You don't need volatile; what you need is to put thread locks around accesses to the list. Since thread locks induce full fences you should not need half fences introduced by volatile.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Thanks a lot Eric. Based on my understanding of your explanation, volatile is used to prevent side effect of memory mutations. However, what is used to prevent CPU register copy of memory value. In multi-core CPU, two cores many cache two copies of variable (for example an int), if one of them update it, how will the other get it. Do we need to do anything in C# code or it is automatically taken cared by the .net? THnaks again. – Frank Dec 29 '11 at 00:47
  • 1
    @Feng: You have misunderstood me. I did not say that "volatile" prevents side effects of memory mutations; how on earth could it do that? Rather, "volatile" affects the *order* in which those mutations might possibly be *observed* to happen. It does so by inducing acquire and release semantics onto variable reads and writes. **If you do not understand how acquire and release semantics work then you should not be using volatile.** Of course to do so it also turns off caching optimizations. – Eric Lippert Dec 29 '11 at 15:52
6

It's worse than that.

If you concurrently access an object that isn't thread-safe, your program may actually crash. Getting out-of-date information is not the worst potential outcome.

When sharing .NET base class library objects between threads, you really have no choice but to use locking. For lockless programming, you need invasive changes to your data structures at the lowest levels.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
2

The volatile keyword has no impact on the content of the list (or, more precisely, the object being referenced).

Speaking about updated / not updated for another thread is an oversimplification of what's happening. You should use the lock statement to synchronize access to the shared list. Otherwise you are effectively facing race conditions that may lead to program crash. The List<T> class is not thread-safe by itself.

Ondrej Tucny
  • 27,626
  • 6
  • 70
  • 90
2

Look at http://www.albahari.com/threading/part4.aspx#_The_volatile_keyword for a good explanation about what volatile actually does and how it impacts fields.

The entire part of threading on that site is a must read anyway, it contains huge amounts of useful information that have proved very useful for me when I was designing multi threaded software.

Drakarah
  • 2,244
  • 2
  • 23
  • 23