Volatile vs. Interlocked vs. lock

Let's say that a class has a public int counter field that is accessed by multiple threads. This int is only incremented or decremented.

To increment this field, which approach should be used, and why?

  • lock(this.locker) this.counter++; ,
  • Interlocked.Increment(ref this.counter); ,
  • Change the access modifier of counter to public volatile .
  • Now that I've discovered volatile , I've been removing many lock statements and the use of Interlocked . But is there a reason not to do this?


    Worst (won't actually work)

    Change the access modifier of counter to public volatile

    As other people have mentioned, this on its own isn't actually safe at all. The point of volatile is that multiple threads running on multiple CPUs can and will cache data and re-order instructions.

    If it is not volatile , and CPU A increments a value, then CPU B may not actually see that incremented value until some time later, which may cause problems.

    If it is volatile , this just ensures the two CPUs see the same data at the same time. It doesn't stop them at all from interleaving their reads and write operations which is the problem you are trying to avoid.

    Second Best:

    lock(this.locker) this.counter++ ;

    This is safe to do (provided you remember to lock everywhere else that you access this.counter ). It prevents any other threads from executing any other code which is guarded by locker . Using locks also, prevents the multi-CPU reordering problems as above, which is great.

    The problem is, locking is slow, and if you re-use the locker in some other place which is not really related then you can end up blocking your other threads for no reason.

    Best

    Interlocked.Increment(ref this.counter);

    This is safe, as it effectively does the read, increment, and write in 'one hit' which can't be interrupted. Because of this it won't affect any other code, and you don't need to remember to lock elsewhere either. It's also very fast (as MSDN says, on modern CPUs this is often literally a single CPU instruction).

    I'm not entirely sure however if it gets around other CPUs reordering things, or if you also need to combine volatile with the increment.

    InterlockedNotes:

  • INTERLOCKED METHODS ARE CONCURRENTLY SAFE ON ANY NUMBER OF COREs OR CPUs.
  • Interlocked methods apply a full fence around instructions they execute, so reordering does not happen.
  • Interlocked methods do not need or even do not support access to a volatile field , as volatile is places a half fence around operations on given field and interlocked is using the full fence.
  • Footnote: What volatile is actually good for.

    As volatile doesn't prevent these kind of multithreading issues, what's it for? A good example is say you have two threads, one which always writes to a variable (say queueLength ), and one which always reads from that same variable.

    If queueLength is not volatile, thread A may write five times, but thread B may see those writes as being delayed (or even potentially in the wrong order).

    A solution would be to lock, but you could also use volatile in this situation. This would ensure that thread B will always see the most up-to-date thing that thread A has written. Note however that this logic only works if you have writers who never read, and readers who never write, and if the thing you're writing is an atomic value. As soon as you do a single read-modify-write, you need to go to Interlocked operations or use a Lock.


    EDIT: As noted in comments, these days I'm happy to use Interlocked for the cases of a single variable where it's obviously okay. When it gets more complicated, I'll still revert to locking...

    Using volatile won't help when you need to increment - because the read and the write are separate instructions. Another thread could change the value after you've read but before you write back.

    Personally I almost always just lock - it's easier to get right in a way which is obviously right than either volatility or Interlocked.Increment. As far as I'm concerned, lock-free multi-threading is for real threading experts, of which I'm not one. If Joe Duffy and his team build nice libraries which will parallelise things without as much locking as something I'd build, that's fabulous, and I'll use it in a heartbeat - but when I'm doing the threading myself, I try to keep it simple.


    " volatile " does not replace Interlocked.Increment ! It just makes sure that the variable is not cached, but used directly.

    Incrementing a variable requires actually three operations:

  • read
  • increment
  • write
  • Interlocked.Increment performs all three parts as a single atomic operation.

    链接地址: http://www.djcxy.com/p/91968.html

    上一篇: Java中的易变Vs静态

    下一篇: 易失与互锁与锁定