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 flag could be provided to primitives.
Although the library is a PCL itself, the minimum required version of .NET - 4.5. But you can compile for .NET 4.0 and earlier. The Itanium-related stuff (volatile reads with proper memory barriers usages, etc.) will be present by using ITANIUM_CPU directive (see docs).
For ECMA MM implementations of CLI on ARM architecture the conditional compilation is supported by using ARM_CPU directive.
The default memory 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 read/writes to particular POD are not supported by HW.
Specifying Acquire only or Release only flag falls back to full Acquire/Release semantics for get/set operations or combinations of.
Atomic<T>
AtomicReference<T>
AtomicInteger
AtomicLong
AtomicBoolean
Read/writes operations on references are provided by AtomicReference<T>
.
The Atomic<T>
class should be used for structs (i.e. value types), including (char
, byte
, etc.).
AtomicInteger
and AtomicLong
classes has support for +, -, *, /, ++, --, +=, -=, *=, /=
operators with atomicity guarantees.
All primitives implement the implicit conversion operator overloads with atomic access.
Integers ranging from 8 to 64 bit are supported as well as unsigned ones.
AtomicInteger
and AtomicLong
classes has support for memory alignment alongside modern CPU's cache lines. Use flag align
in constructor of either Atomic<T>
, AtomicInteger
, AtomicLong
or AtomicBoolean
. Only specializations of Atomic<T>
with Int32, Int64 and Boolean uses alignment.
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 falls back to using AtomicInteger
, AtomicLong
and AtomicBoolean
as internal storage respectively.
The memory order 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.
- RC3:
- New
AtomicReference<T>.Set<TData>(Func<T, TData, T>setter, TData data)
method overload - New byref
Store(ref T value, MemoryOrder order)
method forAtomic<T>
,AtomicInteger
,AtomicLong
andAtomicBoolean
- Optimization of Acquire/Release and Seq_Cst read/writes performance on x86
- ITANIUM_CPU conditional compilation support
- New
- RC2:
align
flag support inAtomic<T>
,AtomicInteger
,AtomicLong
andAtomicBoolean
for false sharing prevention alongside of CPU's cache lines- Bug fixes in CAS loops
- ARM_CPU conditional compilation support
- RC1:
AtomicReference<T>.Set()
method fix for CAS- C++ 11 atomic Load/Store methods as well as IsLockFree property support in primitives
- Docs and samples update
- Beta2:
- Lock-free stack samples
- NuGet package support
- Fixes
- Beta1:
- Thread interruption support in AtomicInteger, AtomicLong
- Docs update
- Alpha:
- Initial milestone of project
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 -Pre
atomics.net is licensed under the BSD license.