Files CCore/inc/sys/SysAtomic.h CCore/src/sys/SysAtomic.cpp CCore/src/sys/SysAtomic.s
This part is a target-dependent atomic operations implementation. It's done using the structure Atomic.
#ifndef CCore_inc_sys_SysAtomic_h #define CCore_inc_sys_SysAtomic_h namespace CCore { namespace Sys { /* classes */ struct Atomic; /* struct Atomic */ struct Atomic { // private .... // public using Type = .... ; // unsigned integral type using PrintProxyType = Type ; void set_null(); operator Type() const; // return previous value, memory fence is used Type operator = (Type value); Type operator += (Type value); Type operator -= (Type value); Type operator ++ (int); Type operator -- (int); Type trySet(Type old_value,Type new_value); }; } // namespace Sys } // namespace CCore #endif
This structure has only one non-static data member of the type Atomic::Type. So you may do a static initialization of a variable of this type:
static Sys::Atomic SomeAtomic = {0} ;
You may copy this structure, but it is not atomic and doing so is not recommended.
set_null() sets the atomic value to the null, this is not an atomic operation! Other operations are atomic.
operator Type() reads the value of the atomic. This operation is a read operation and it does not apply any memory fence, only atomicity is guaranteed. Other operations are modifying operations, they returns the previous value of the atomic and apply a memory fence.
operator = sets the new value to the atomic.
operator += increases the value of the atomic by the argument.
operator -= decreases the value of the atomic by the argument.
postfix operator ++ increments the value of the atomic.
postfix operator -- decrements the value of the atomic.
trySet() is a conditional set operation. It does the following:
auto ret=value; if( value==old_value ) value=new_value; return ret;
In the following example,
int a=0; Sys::Atomic b={0}; // thread 1 a=1; b=1; // thread 2 while( b==0 ); int c=a;
c is guaranteed to be 1. The implementation requires not only a proper hardware support, but also the prevention the compiler to overoptimize the atomic operations.
Here is a typical structure implementation. Usually you have to give an atomic type and tree atomic functions: Set(), Add() and TrySet(). These functions are typically implemented using an assembler. Each of them applies a memory-fence operation to make sure any changes of any variable, made before the function call, becomes visible to any thread of execution.
struct Atomic { // public using Type = .... ; // unsigned integral type // private data volatile Type atomic; // private static Type Get(const volatile Type *atomic) { return *atomic; } static Type Set(volatile Type *atomic,Type value) noexcept; static Type Add(volatile Type *atomic,Type value) noexcept; static Type Sub(volatile Type *atomic,Type value) { return Add(atomic,-value); } static Type Inc(volatile Type *atomic) { return Add(atomic,1); } static Type Dec(volatile Type *atomic) { return Sub(atomic,1); } static Type TrySet(volatile Type *atomic,Type old_value,Type new_value) noexcept; // // if( *atomic==old_value ) *atomic=new_value; // // public using PrintProxyType = Type ; void set_null() { atomic=0; } operator Type() const { return Get(&atomic); } // return previous value, memory fence is used Type operator = (Type value) { return Set(&atomic,value); } Type operator += (Type value) { return Add(&atomic,value); } Type operator -= (Type value) { return Sub(&atomic,value); } Type operator ++ (int) { return Inc(&atomic); } Type operator -- (int) { return Dec(&atomic); } Type trySet(Type old_value,Type new_value) { return TrySet(&atomic,old_value,new_value); } };
You may derive functions Sub(), Inc(), Dec() from the Add() as shown above, or give an independent implementation.
The function Get() usually can be implemented directly, as shown above, but you can provide another implementation, if necessary.
Get() reads the value of the atomic. No memory fence operation is applied (but atomicity is guaranteed).
Set() sets the new value to the atomic. The old value is returned. A memory fence is applied.
Add() adds the value to the atomic. The old value is returned. A memory fence is applied.
TrySet() performs the conditional set. If the value of the atomic equals the old_value, the new_value is set. The previous value is returned. A memory fence is applied.