How to read Interlocked.Increment 'int field correctly?

Suppose I have a non-volatile int field and a thread that Interlocked.Increment it. Can another thread safely read this directly or do I also need to block reading?

Earlier, I thought that I needed to use interconnected reading to ensure that I see the current value, because, after all, the field is not mutable. I used Interlocked.CompareExchange(int, 0, 0) to achieve this.

However, I came across this answer , which says that in fact simple reads will always see the current version of the Interlocked.Increment ed value, and since int reading is already atomic, there is no need to do anything special. I also found a request in which Microsoft rejects the Interlocked.Read (ref int) request , which also indicates that this is completely redundant.

So, can I really read the most current value of such an int field without Interlocked ?

+48
multithreading c # thread-safety
May 26 '11 at 13:55
source share
4 answers

If you want to guarantee that another thread will read the last value, you should use Thread.VolatileRead() . (*)

The read operation itself is atomic, so this will not cause any problems, but without volatile reading you can get the old value from the cache, and the compiler can optimize your code and completely exclude the read operation. From the point of view of the compiler, it is enough for the code to work in a single-threaded environment. Volatile operations and memory barriers are used to limit the compiler's ability to optimize and reorder code.

There are several contributors who can change the code: the compiler, the JIT compiler, and the processor. It doesn't really matter which one shows that your code is broken. The only important thing is the .NET memory model, because it defines the rules that must be followed by all participants.

(*) Thread.VolatileRead() does not really get the last value. It will read the value and add a memory barrier after reading. The first volatile read can get the cached value, and the second will get the updated value, because the memory barrier of the first modified read forced the cache to be updated if necessary. In practice, this detail does not matter much when writing code.

+19
May 26 '11 at 14:01
source share

A bit of a meta problem, but a good aspect about using Interlocked.CompareExchange(ref value, 0, 0) (ignoring the obvious drawback, which is harder to understand when using for reading) is that it works regardless of int or long . It is true that reading int always atomic, but long reading is not or cannot be, depending on the architecture. Unfortunately, Interlocked.Read(ref value) only works if value is of type long .

Consider the case when you start with the int field, which makes it impossible to use Interlocked.Read() , so you will read the value directly instead of this atom in any case. However, later in development, you or someone else decided that a long is required - the compiler will not warn you, but now you may have a subtle error: read access is not guaranteed to be atomic. I found here Interlocked.CompareExchange() better alternative; It may be slower depending on the basic instructions of the processor, but in the long run it is safer. I do not know enough about internal Thread.VolatileRead() ; This may be โ€œbetterโ€ with respect to this use case, as it provides even more signatures.

I would not try to read the value directly (i.e. without any of the above mechanisms) in a loop or any narrow method, though, since even if the records are unstable and / or memory barriers, the compiler says nothing that the field value can actually change between two readings. Thus, the field must be either volatile , or any of these constructs must be used.

My two cents.

+12
Mar 28 '14 at 13:33
source share

You are right that you do not need special instructions for atomic reading of a 32-bit integer, however this means that you will get an โ€œintegerโ€ value (that is, you will not get part of one record and part of another). You have no guarantee that the value will not change after reading it.

It is at this point that you need to decide whether you need to use some other synchronization method to control access, say, if you use this value to read a member from an array, etc.




In a nutshell, atomicity ensures that the operation is complete and indivisible. Given some operation A containing steps N , if you did this immediately after A , you can be sure that all steps N performed in isolation from parallel operations.

If you had two threads that performed atomic operation A , you are guaranteed to see only the result of complete from one of the two threads. If you want to coordinate threads, you can use atomic operations to create the required synchronization. But atomic operations alone do not provide higher level synchronization. The Interlocked family of methods is available to provide some fundamental atomic operations.

Synchronization is a broader type of concurrency control that is often built around atomic operations. Most processors include memory barriers that allow you to clear all cache lines, and you have a consistent view. Volatile reads are a way to provide consistent access to a specific memory location.

If you are not immediately applicable to your problem, reading ACID information (atomicity, consistency, isolation, and durability) regarding databases can help you in terminology.

+8
May 26 '11 at 14:01
source share

Yes, everything you read is true. Interlocked.Increment is designed so that normal readings are not false when making changes to the field. Reading a field is not dangerous by writing a field.

-5
May 26 '11 at 14:00
source share



All Articles