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
Most Efficient Way to Test Equality of Lambda Expressions
Using Linq to Concatenate Strings
How to Enable Design Support in a Custom Control
Shorter Syntax for Casting from a List<X> to a List<Y>
How to Get the File Size from Http Headers
Why Does This Async Action Hang When I Try and Access the Result Property of My Task
How to Call the 'Base Implementation' of an Overridden Virtual Method
Why Does This Floating-Point Calculation Give Different Results on Different MAChines
How to Download HTML Source in C#
How to Drag and Drop Files into an Application
Headless Browser for C# (.Net)
Validate Image from File in C#
Impossible to Use Ref and Out for First ("This") Parameter in Extension Methods