What Operations Are Atomic in C#

What operations are atomic in C#?

For something more complete/detailed:

Reads and writes to 32-bit value types are atomic: This includes the following intrinsic value (struct) types: bool, char, byte, sbyte, short, ushort, int, uint, float. The following types (amongst others) are not guaranteed to be atomic: decimal, double, long, ulong.

e.g.

int x;
x = 10; // atomic
decimal d;

d = 10m; // not atomic

Reference assignment is also an atomic operation:

private String _text;
public void Method(String text)
{
_text = text; // atomic
}

In C#, what does atomic mean?

Atomic operations are ones that cannot be interrupted partway through, such as by threading. Take for instance the statement

_value++;

If you have two threads executing this code at once with a starting value of 0, you may have the following

  • Thread A reads _value, 0
  • Thread A adds 1, 1
  • Thread B reads _value, 0
  • Thread B adds 1, 1
  • Thread A assigns to _value, 1
  • Thread B assigns to _value, 1

so now, even though we've called an increment twice, the final value in _value is 1, not the expected 2. This is because increment operators are not atomic.

The function Interlocked.Increment, however, is atomic, so replacing the above code with

Interlocked.Increment(ref _value);

Would solve the given race condition.

EDIT: As a point of etymology, "atomic" usually means "indivisible" - the chemistry term we're familiar with is a misnomer held over from the belief that atoms were indivisible, only for later discoveries to break them down further into subatomic, quark, and quanta levels.

Is accessing a variable in C# an atomic operation?

For the definitive answer go to the spec. :)

Partition I, Section 12.6.6 of the CLI spec states: "A conforming CLI shall guarantee that read and write access to properly aligned memory locations no larger than the native word size is atomic when all the write accesses to a location are the same size."

So that confirms that s_Initialized will never be unstable, and that read and writes to primitve types smaller than 32 bits are atomic.

In particular, double and long (Int64 and UInt64) are not guaranteed to be atomic on a 32-bit platform. You can use the methods on the Interlocked class to protect these.

Additionally, while reads and writes are atomic, there is a race condition with addition, subtraction, and incrementing and decrementing primitive types, since they must be read, operated on, and rewritten. The interlocked class allows you to protect these using the CompareExchange and Increment methods.

Interlocking creates a memory barrier to prevent the processor from reordering reads and writes. The lock creates the only required barrier in this example.

If an operation on a float is atomic in .NET then why do I get different values here?

Reading and writing floats is atomic, this is guaranteed by the language. However, operations on floats are not atomic. There is no way around using some kind of syncing in this case.

On a sidenote, you have a small race condition, the while might exit before any thread was ever started.

Are basic arithmetic operations in C# atomic

The spec sums it up very well. Section 5.5, "Atomicity of variable references":

Reads and writes of the following data types are atomic: bool, char,
byte, sbyte, short, ushort, uint, int, float, and reference types. In
addition, reads and writes of enum types with an underlying type in
the previous list are also atomic. Reads and writes of other types,
including long, ulong, double, and decimal, as well as user-defined
types, are not guaranteed to be atomic. Aside from the library
functions designed for that purpose, there is no guarantee of atomic
read-modify-write, such as in the case of increment or decrement.

Conclusions:

  • Independent reads/writes are atomic (but only for some data types)
  • Read/modify/write (such as i++) is never atomic
  • You can use the Interlocked class methods to achieve atomicity when it's not already guaranteed

In cases where Interlocked functionality is not enough there is no other option than to use a synchronization primitive, such as Monitor.Enter (which the compiler also exposes through the lock statement).

Is array write atomic in C#?

Operation 1 is atomic. Operation 2 is not. From the spec:

5.5 Atomicity of variable references

Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types. In addition, reads and writes of enum types with an underlying type in the previous list are also atomic. Reads and writes of other types, including long, ulong, double, and decimal, as well as user-defined types, are not guaranteed to be atomic.

Arrays are reference types. Variable a and b are references, and so Operation 1 is a reference assignment: a simple write to a reference variable, and so is included. Operation 2 looks like a simple write to a bool, which would also be included, but don't forget the index lookup in the array. The array write itself is atomic, but when you include the lookup (dereferencing a[1]) there are two separate operations involved.

Is a bool read/write atomic in C#

Yes.

Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types.

as found in C# Language Spec.

Edit: It's probably also worthwhile understanding the volatile keyword.

C# thread-safe atomic operation on item in ConcurrentDictionary

The ConcurrentDictionary is thread-safe in the sense that it protects its internal state from corruption. It does not protect from corruption the state of the objects it contains as keys or values. After retrieving an object from the ConcurrentDictionary using the GetOrAdd method, if you want to mutate the state of this object and the object is not thread-safe, you are responsible for synchronizing the access to this object from multiple threads by using locks or other means. In other words, the thread-safety guaranties offered by the ConcurrentDictionary class are limited to itself.

You may still want to use this class, even if it does not cover all your thread-safety needs, because its granular internal locking implementation can be quite efficient in cases of high thread contention.



Related Topics



Leave a reply



Submit