views:

573

answers:

8

I'm confused. Answers to my previous question seems to confirm my assumptions. But as stated here volatile is not enough to assure atomicity in .Net. Either operations like incrementation and assignment in MSIL are not translated directly to single, native OPCODE or many CPUs can simultaneously read and write to the same RAM location.

To clarify:

  1. I want to know if writes and reads are atomic on multiple CPUs?
  2. I understand what volatile is about. But is it enough? Do I need to use interlocked operations if I want to get latest value writen by other CPU?
A: 

Volatile is a compiler keyword that tells the compiler what to do. It does not necessarily translate into (essentially) bus operations that are required for atomicity. That is usually left up to the operating system.

Edit: to clarify, volatile is never enough if you want to guarantee atomicity. Or rather, it's up to the compiler to make it enough or not.

MSN
duh. but what about incrementation and assignment?
Jacek Ławrynowicz
If you are talking about interlocked operations, i.e., guaranteed atomic operations, those two need to be implemented either by bus operations (x86 uses the lock opcode modifier) or the OS (PPC needs to use lwarx/stwrx to do the equivalent).
MSN
+2  A: 

volatile in .NET does make access to the variable atomic.

The problem is, that's often not enough. What if you need to read the variable, and if it is 0 (indicating that the resource is free), you set it to 1 (indicating that it's locked, and other threads should stay away from it).

Reading the 0 is atomic. Writing the 1 is atomic. But between those two operations, anything might happen. You might read a 0, and then before you can write the 1, another thread jumps in, reads the 0, and writes an 1.

However, volatile in .NET does guarantee atomicity of accesses to the variable. It just doesn't guarantee thread safety for operations relying on multiple accesses to it. (Disclaimer: volatile in C/C++ does not even guarantee this. Just so you know. It is much weaker, and occasinoally a source of bugs because people assume it guarantees atomicity :))

So you need to use locks as well, to group together multiple operations as one thread-safe chunk. (Or, for simple operations, the Interlocked operations in .NET may do the trick)

jalf
A: 

Your question doesn't entirely make sense, because volatile specifies the how the read happens, not atomicity of multi-step processes. My car doesn't mow my lawn, either, but I try not to hold that against it. :)

Craig Stuntz
A: 

The problem comes in with register based cashed copies of your variable's values.

When reading a value, the cpu will first see if it's in a register (fast) before checking main memory (slower).

Volatile tells the compiler to push the value out to main memory asap, and not to trust the cached register value. It's only useful in certain cases.

If you're looking for single op code writes, you'll need to use the Interlocked.Increment related methods.. But they're fairly limited in what they can do in a single safe instruction.

Safest and most reliable bet is to lock() (if you can't do an Interlocked.*)

Edit: Writes and reads are atomic if they're in a lock or an interlocked.* statement. Volatile alone is not enough under the terms of your question

Peter Drier
+5  A: 

I might be jumping the gun here but it sounds to me as though you're confusing two issues here.

One is atomicity, which in my mind means that a single operation (that may require multiple steps) should not come in conflict with another such single operation.

The other is volatility, when is this value expected to change, and why.

Take the first. If your two-step operation requires you to read the current value, modify it, and write it back, you're most certainly going to want a lock, unless this whole operation can be translated into a single CPU instruction that can work on a single cache-line of data.

However, the second issue is, even when you're doing the locking thing, what will other threads see.

A volatile field in .NET is a field that the compiler knows can change at arbitrary times. In a single-threaded world, the change of a variable is something that happens at some point in a sequential stream of instructions so the compiler knows when it has added code that changes it, or at least when it has called out to outside world that may or may not have changed it so that once the code returns, it might not be the same value it was before the call.

This knowledge allows the compiler to lift the value from the field into a register once, before a loop or similar block of code, and never re-read the value from the field for that particular code.

With multi-threading however, that might give you some problems. One thread might have adjusted the value, and another thread, due to optimization, won't be reading this value for some time, because it knows it hasn't changed.

So when you flag a field as volatile you're basically telling the compiler that it shouldn't assume that it has the current value of this at any point, except for grabbing snapshots every time it needs the value.

Locks solve multiple-step operations, volatility handles how the compiler caches the field value in a register, and together they will solve more problems.

Also note that if a field contains something that cannot be read in a single cpu-instruction, you're most likely going to want to lock read-access to it as well.

For instance, if you're on a 32-bit cpu and writing a 64-bit value, that write-operation will require two steps to complete, and if another thread on another cpu manages to read the 64-bit value before step 2 has completed, it will get half of the previous value and half of the new, nicely mixed together, which can be even worse than getting an outdated one.


Edit: To answer the comment, that volatile guarantees the atomicity of the read/write operation, that's well, true, in a way, because the volatile keyword cannot be applied to fields that are larger than 32-bit, in effect making the field single-cpu-instruction read/writeable on both 32 and 64-bit cpu's. And yes, it will prevent the value from being kept in a register as much as possible.

So part of the comment is wrong, volatile cannot be applied to 64-bit values.

Note also that volatile has some semantics regarding reordering of reads/writes.

For relevant information, see the MSDN documentation or the C# specification, found here, section 10.5.3.

Lasse V. Karlsen
Just to be clear, your description of volatile sounds more like the one found in C/C++. In .net, volatile does guarantee atomicity as well as forcing it to be kept in memory, rather than a register. Even on larger values, like 64-bit.
jalf
A: 

Volatile is only intended to identify the fact that the value should be re-read. Sometimes (apparently) a value is read and then cached. If it is marked as volatile it will be re-read every time you read its value.

It's not meant to be read or write safe, it's just a way of saying "I don't use locking to write this, so don't rely on a cache". That's my interpretation of what I read in my current book anyway :-)

Peter Morris
If you down voted me then you obviously think I am wrong, so at least have the balls to expose your own opinion and post a comment as to why you think this is the case.
Peter Morris
+6  A: 

Herb Sutter recently wrote an article on volatile and what it really means (how it affects ordering of memory access and atomicity) in the native C++. .NET, and Java environments. It's a pretty good read:

Michael Burr
A: 

On a hardware level, multiple CPUs can never write simultanously to the same atomic RAM location. The size of an atomic read/write operation dependeds on CPU architecture, but is typically 1, 2 or 4 bytes on a 32-bit architecture. However, if you try reading the result back there is always a chance that another CPU has made a write to the same RAM location inbetween. On a low level, spin-locks are typically used to synchronize access to shared memory. In a high level language, such mechanisms may be called e.g. critical regions.

The volatile type just makes sure the variable is written immediately back to memory when it is changed (even if the value is to be used in the same function). A compiler will usually keep a value in an internal register for as long as possible if the value is to be reused later in the same function, and it is stored back to RAM when all modifications are finished or when a function returns. Volatile types are mostly useful when writing to hardware registers, or when you want to be sure a value is stored back to RAM in e.g. a multithread system.