85

I have a variable which I am using to represent state. It can be read and written to from multiple threads.

I am using Interlocked.Exchange and Interlocked.CompareExchange to change it. However I am reading it from multiple threads.

I know that volatile can be used to make sure the variable is not cached locally but always reads directly from memory.

However if I set the variable to volatile then it generates a warning about using volatile and passing using ref to the Interlocked methods.

I want to ensure that each thread is reading the most recent value of the variable and not some cached version, but I can't use volatile.

There is a Interlocked.Read but it is for 64 bit types and is not available on the compact framework. The documentation for it says that it is not needed for 32 bit types as they are already performed in a single operation.

There are statements made across the internet that you don't need volatile if you are using the Interlocked methods for all your access. However you can't read a 32 bit variable using the Interlocked methods, so there is no way you can use Interlocked methods for all your access.

Is there some way to accomplish the thread safe read and write of my variable without using lock?

Stormenet
  • 25,926
  • 9
  • 53
  • 65
trampster
  • 8,598
  • 4
  • 37
  • 52
  • 2
    Good question actually. Using a regular lock implies a *critical execution point* and guarantees your value will be up to date for all threads. However, Interlocked.Exchange is not implemented using `lock` and I can't find any reference that it makes such guarantees. – Thorarin Jul 27 '09 at 05:17

2 Answers2

51

Interlocked operations and volatile are not really supposed to be used at the same time. The reason you get a warning is because it (almost?) always indicates you have misunderstood what you are doing.

Over-simplifying and paraphrasing:
volatile indicates that every read operation needs to re-read from memory because there might be other threads updating the variable. When applied to a field that can be read/written atomically by the architecture you are running on, this should be all you need to do unless you are using long/ulong, most other types can be read/written atomically.

When a field is not marked volatile, you can use Interlocked operations to make a similar guarantee, because it causes the cache to be flushed so that the update will be visible to all other processors... this has the benefit that you put the overhead on the update rather than the read.

Which of these two approaches performs best depends on what exactly you are doing. And this explanation is a gross over-simplification. But it should be clear from this that doing both at the same time is pointless.

jerryjvl
  • 19,723
  • 7
  • 40
  • 55
  • OK so if I'm only using Interlocked to write then normals reads will always be up to date? – trampster Jul 27 '09 at 05:44
  • Generally that should do the job, yes... that's why (other than for long/ulong) there are not Interlocked operations for just reading values. – jerryjvl Jul 27 '09 at 05:52
  • 3
    There is nothing inherently wrong with using volatile and Interlocked together. The reason you get a warning is that `volatile` is that `volatile` is not a part of the type system, and the callee who receives the reference will not know that the referenced variable needs volatile access. – Anton Tykhyy Jul 27 '09 at 06:20
  • 1
    Yeah, my first sentence could have been clearer... The warning is not because of mixing 'Interlocked' and 'volatile' as such, but because of mixing 'ref' and 'volatile'. Still does not change the fact that the warning indicates a misunderstanding of what is going on. – jerryjvl Jul 27 '09 at 08:34
  • 2
    For the record, there's [discussion over here](http://stackoverflow.com/questions/6139699/how-to-correctly-read-an-interlocked-incremented-int-field) where it is suggested that this answer is wrong. @jerryjvl, if you wish to contribute that would be great. – Roman Starkov May 26 '11 at 17:13
  • 2
    There's more subtlety involved in this than I had realised when I first wrote this answer. I strongly recommend reading Joe Duffy (and/or his book on concurrency) to get a detailed understanding in the context of the problem being addressed. He's really the authority when it comes to these matters. The big issue with lock-free is that it usually is about performance in some sense, but there are pitfalls along the way that can either produce incorrect results or degrade the performance being sought. – jerryjvl Jul 05 '11 at 05:11
  • 9
    Joe Duffy talks specifically about the reference to volatile / Interlocked operation in [this blog entry](http://www.bluebytesoftware.com/blog/CommentView,guid,c36d1633-50ab-4462-993e-f1902f8938cc.aspx). Mixing interlocked with references is 100% ok, and he's petitioned to add a special case in the C# compiler not to throw a warning in this case. – Chuu Nov 09 '11 at 18:51
  • Thanks for finding that reference... at the time I referred to Joe Duffy his site was down, and I was only hazily remembering what he'd written. – jerryjvl Nov 10 '11 at 03:18
  • So the main issue is that the compiler (or JIT) may optimize away the direct memory access for threads which only read the value unless you mark the variable `volatile`, right? – binki Sep 02 '15 at 17:53
  • @binki - Late response but...I think the issue is more to do with the fact that one processor core might cache the memory read so it won't see an update done by another core on subsequent reads. – Mike Marynowski Mar 11 '17 at 12:04
  • 5
    Current links to the Joe duffy blog in it's new home http://joeduffyblog.com/2008/06/13/volatile-reads-and-writes-and-timeliness/ http://joeduffyblog.com/2009/02/02/c-compiler-warning-cs0420-byrefs-to-volatiles/ http://joeduffyblog.com/2007/11/10/clr-20-memory-model/ – CCondron Jun 27 '18 at 20:01
  • Let's say `x` is a shared variable. In one thread I do `a+=x; a+=x;` with `a` being local to the thread. In a second thread I do `Interlocked.Increment(ref x);`. Without `volatile`, the first thread may put `x` in a register, and use it for both its statements, missing the increment. So `volatile` seems necessary, even with `Interlocked`. – wezten Jan 07 '21 at 18:52
49

You can safely disregard that warning when you're using Interlocked.Xxx functions (see this question), because they always do volatile operations. So a volatile variable is perfectly OK for shared state. If you want to get rid of the warning at all costs, you actually can do an interlocked read with Interlocked.CompareExchange (ref counter, 0, 0).

Edit: Actually, you need volatile on your state variable only if you are going to write to it directly (i.e. not using Interlocked.Xxx). As jerryjvl mentioned, reads of a variable updated with an interlocked (or volatile) operation will use the most recent value.

Community
  • 1
  • 1
Anton Tykhyy
  • 19,370
  • 5
  • 54
  • 56
  • Where I should replace 0 with a value which my variable will never be? – trampster Jul 27 '09 at 05:18
  • 3
    Phew, if it didn't, Interlocked would be pretty useless. Scared me there for a minute :) – Thorarin Jul 27 '09 at 05:22
  • @Daniel: no, the constant doesn't matter, because `CompareExchange` will be replacing `0` with `0` if `counter` is `0` — i.e. a no-op. – Anton Tykhyy Jul 27 '09 at 06:15
  • 2
    Are reads of a non-`volatile` variable protected from being optimized away by the compiler or JIT? Isn’t the edit referring to @jerryjvl incorrect? – binki Sep 02 '15 at 17:54
  • Let's say `x` is a shared variable. In one thread I do `a+=x; a+=x;` with `a` being local to the thread. In a second thread I do `Interlocked.Increment(ref x);`. Without `volatile`, the first thread may put `x` in a register, and use it for both its statements, missing the increment. So `volatile` seems necessary, even with `Interlocked`. – wezten Jan 07 '21 at 18:58