This package enables .NET projects to use atomic primitives.
Project aims to be very close to C++ 11 standard atomics by design and usage. For example, the memory order semantics is supported.
Although the library is a PCL itself, the minimum required version of .NET is 4.5. It is possible to compile and use for .NET 4.0 and earlier. ARM-related stuff (volatile reads with proper memory barriers usages, etc.) will be present by using ARM_CPU directive (see docs).
The default memory order semantics for the library's primitives (like Atomic<T>
, etc.) is MemoryOrder.SeqCst
, whereas AtomicReference<T>
uses MemoryOrder.AcqRel
, which fits very well with CAS approach and CLR 2.0 memory model.
The option for sequential consistency (i.e. SeqCst
) is implemented by using intrinsic functions (with compilation to proper CPU instruction) or a combination of Acquire/Release with sequential order emulation through exclusion locks, when atomic reads/writes to particular POD are not supported by HW.
Atomic<T>
AtomicReference<T>
AtomicInteger
AtomicLong
AtomicBoolean
AtomicReferenceArray
AtomicIntegerArray
AtomicLongArray
Reads/writes operations for reference types are provided by AtomicReference<T>
.
The Atomic<T>
type should be used for structs (i.e. value types), including (char
, byte
, etc.).
AtomicInteger
and AtomicLong
types have support for +, -, *, /, ++, --
operators with atomicity guarantees.
All primitives implement the implicit conversion operator overload with atomic access.
Integers ranging from 8 to 64 bits are supported as well as unsigned ones.
AtomicInteger
and AtomicLong
types has support for memory alignment alongside modern CPU's cache lines. Use flag align
in constructors of either Atomic<T>
, AtomicInteger
, AtomicLong
or AtomicBoolean
. Only specializations of Atomic<T>
with Int32
, Int64
and Boolean
have effect.
Here is the basic setup and usage of atomic primitives.
using System;
class Counter
{
private AtomicInteger _value;
private readonly bool _isReadOnly;
public Counter(int initialValue = 0, bool isReadOnly = false)
{
/*
* _value = new AtomicInteger(align: true)
* for false sharing prevention, otherwise as shown below
*/
_value = initialValue;
_isReadOnly = isReadOnly;
}
public void Increment(int value)
{
if (!_isReadOnly)
_value++;
}
public void PrintCounter()
{
Console.WriteLine(_value); // Console.WriteLine(int) overload will be used
}
}
Atomic<T>
with Int32
, Int64
and Boolean
specialization fallbacks to AtomicInteger
, AtomicLong
and AtomicBoolean
usage as internal storage respectively.
The memory ordering flag as well as alignment transfers to internal storage.
It is very straightforward to implement lock-free stack:
public class AtomicStack<T>
{
private AtomicReference<StackNode<T>> _headNode = new AtomicReference<StackNode<T>>();
public void Push(T item)
{
_headNode.Set((stackNode, data) =>
{
StackNode<T> node = new StackNode<T>(data);
node._next = stackNode;
return node;
}, item);
}
public T Pop()
{
if (IsEmpty)
throw new InvalidOperationException();
return _headNode.Set(stackNode => stackNode._next)._value;
}
public bool IsEmpty
{
get { return _headNode.Load(MemoryOrder.Acquire) == null; }
}
class StackNode<T>
{
internal T _value;
internal StackNode<T> _next;
internal StackNode(T val) { _value = val; }
}
}
and usage:
AtomicStack<int> stack = new AtomicStack<int>();
Parallel.For(0, 100000, stack.Push);
var thread = new Thread(() => Parallel.For(0, 50000, index => stack.Pop()));
thread.IsBackground = true;
thread.Start();
int i = 0;
while (!stack.IsEmpty)
{
stack.Pop();
i++;
}
Console.WriteLine("Pushed: {0};", i); // should print 50000
For more details about AtomicStack<T>
example above, please refer docs.
Usually compare-and-swap (CAS) is used in lock-free algorithms to maintain thread-safety, while avoiding locks. Especially often the compare_exchange_weak
variation is used.
Provided by the .NET Framework Interlocked.CompareExchange
method is the C++ compare_and_exchange_strong
analog. The compare_exchange_weak
is not supported.
Current implementation of atomics.net uses CAS approach for lock-free atomic operations (the Atomic<T>.Value
property uses CAS for setter in Acquire/Release mode.
Feel free to fork and create pull-requests if you have any kind of enhancements and/or bug fixes.
Command: PM> Install-Package System.Threading.Atomics
atomics.net is licensed under the BSD license.