views:

456

answers:

3

Win32 api has a set of InterlockedXXX functions to atomically and synchronously manipulate simple variables, however there doesn't seem to be any InterlockedRead function, to simply retrive the value of the variable. How come?

MSDN says that "Simple reads and writes to properly-aligned 32-bit variables are atomic operations", but adds that "However, access is not guaranteed to be synchronized", which means, as I understand it, that a simple read operation of a variable can take place while another, say, InterlockedAdd operation is in place. So why isn't there an interlocked function to read a variable?

I guess the value can be read as the result InterlockedAdd-ing zero, but that doesn't seem the right way to go.

+2  A: 

The normal way of implementing this is to use a compare-exchange operation (e.g. InterlockedCompareExchange64) where both values are the same. I have a sneaking suspicion this can be performed more efficiently than an add of 0 for some reason, but I have no evidence to back this up.

Interestingly, .NET's Interlocked class didn't gain a Read method until .NET 2.0. I believe that Interlocked.Read is implemented using Interlocked.CompareExchange. (Note that the documentation for Interlocked.Read strikes me as somewhat misleading - it talks about atomicity, but not volatility, which means something very specific on .NET. I'm not sure what the Win32 memory model guarantees about visibility of newly written values from a different thread, if anything.)

Jon Skeet
For volatility you have Thread.VolatileRead family.
Remus Rusanu
@JS: Is there a specific reason you advised InterlockedCompareExchange64 (with emphasis on 64)? Interlocked.Read also works with 64 bits values, and says that "The Read method is unnecessary on 64-bit systems", which somewhat implies that InterlockedCompareExchnage (32 bits version) is not necessary when reading 32 bit values on a 32 bit machine.
@Remus: Good point about Thread.VolatileRead, although the documentation implies that you don't need to worry *at all* (rather than pointing to Thread.VolatileRead, which would have made more sense). @Sause: No particular reason for picking the 64 bit version, no. I think there's enough subtlety here that I wouldn't like to claim exactly what's necessary when in terms of trying to get away without doing any of this.
Jon Skeet
Of course, I've been assuming that Interlocked.Read will act as a volatile read in the first place. It never mentioned that in the docs, although I certainly hope it's the case!
Jon Skeet
I'm taking a risk here with 3k vs 70k plus, but I thought that there wasn't anything too special in the interlocked classes. The reason they have that name is that because of the X86 architecture the interlocked operations of add and compare etc execute instructions that are guaranteed to complete atomically before a thread switch. Hence they are "interlocked" because they run in parallel in the processor pipeline. I'm fairly sure the docs also say that interlocked is not guaranteed away from a X86 processor. http://msdn.microsoft.com/en-us/library/sbhbke0y.aspx
Spence
What do you mean by "not guaranteed away"? I certainly hope it's guaranteed to behave properly...
Jon Skeet
+3  A: 

I think that your interpretation of "not synchronized" is wrong. Simple reads are atomic, but you have to take care of reordering and memory visibility issues yourself. The former is handled by using fence instructions at appropriate places, the latter is a non-issue with read (but a potential concurrent write has to ensure proper visibility, which Interlocked functions should do if they map to LOCKED asm instructions).

zvrba
Glad to hear my concerns weren't completely stupid :)
Jon Skeet
@zvrba: I think that what I call synchronized (actually, what MSDN calls synchronized) is what you call reordered. Anyway, I don't really see what the bottom line here is. Is this an answer or a comment?
This is an answer -- interlocked read (whatever that might be) is not necessary.
zvrba
@zvrba: There is the issue of reading 64bit values on 32bit processors which is not atomic AFAIK. Hence CMPXCHG8B and Thread.InterlockedRead since you can actually read half of value pre-update, half post-update (ie. atomicity, not reordering).
Remus Rusanu
AFAIK, bus transactions can be larger than 32 bits even on 32-bit x86. You could use 64-bit floating-point or MMX loads to read a 64-bit value atomically if the value is aligned to 64-bits. (from assembly. it's probably not possible within CLR due to many ugly casts that would be involved.)
zvrba
The thread starting with this post may also be insightful:http://newsgroups.derkeiler.com/Archive/Comp/comp.programming.threads/2006-03/msg00003.html
zvrba
A: 

The crux of this whole discussion is proper alignment, which is devined in Partition I of xxx, in section '12.6.2 Alignment':

Built-in datatypes shall be properly aligned, which is defined as follows:
• 1-byte, 2-byte, and 4-byte data is properly aligned when it is stored at
  a 1-byte, 2-byte, or 4-byte boundary, respectively.
• 8-byte data is properly aligned when it is stored on the same boundary
  required by the underlying hardware for atomic access to a native int.

Basically, all 32-bit values have the required alignment, and on a 64-bit platform, 64-bit values also have the required alignment.

Note though: There are attributes to explicitly alter the layout of classes in memory, which may cause you to lose this alignment. These are attributes specificially for this purpose though, so unless you have set out to alter the layout, this should not apply to you.

With that out of the way, the purpose of the Interlocked class is to provide operations that (to paraphrase) can only be observed in their 'before' or 'after' state. Interlocked operations are normally only of concern when modifying memory (typically in some non-trivial compare-exchange type way). As the MSDN article you found indicates, read operations (when properly aligned) can be considered atomic at all times without further precautions.

There are however other considerations when dealing with read operations:

  • On modern CPUs, although the read may be atomic, it also may return the wrong value from a stale cache somewhere... this is where you may need to make the field 'volatile' to get the behaviour you expect
  • If you are dealing with a 64-bit value on 32-bit hardware, you may need to use the Interlocked.Read operation to guarantee the whole 64-bit value is read in a single atomic operation (otherwise it may be performed as 2 separate 32-bit reads which can be from either side of a memory update)
  • Re-ordering of your reads / writes may cause you to not get the value you expected; in which case some memory barrier may be needed (either explicit, or through the use of the Interlocked class operations)

Short summary; as far as atomicity goes, it is very likely that what you are doing does not need any special instruction for the read... there may however be other things you need to be careful of, depending on what exactly you are doing.

jerryjvl