Friday, February 28, 2025

Lock free programming

Overview
The atomic classes which provide synchronization thru interlocked atomic operations.

Details
atomic
An atomic class is template based and its instances are guaranteed to be thread safe during access, updates.  
Certain POD types are also supported.
Specialization classes are defined for scalar data types such as boolean, integers, floating point, pointers etc, which provide additional atomic arithmetic and logical operations.
Additionally, atomic objects have the ability to synchronize access to other non-atomic objects in their threads by specifying different memory orders.

syntax
template< class T >
struct atomic
//specialization for pointers
template< class U >
struct atomic<U*>

member types
NameDescription
value_typeTemplate parameter - type of the data

constructors
NameDescription
  1. atomic()
  2. atomic (T val
  1. Default constructor
  2. Initialization constructor

overloaded operators
NameDescription
 T operator= (T val)Atomically replaces the value with val.
operator T()Atomically loads and returns the current value of the atomic variable. Equivalent to load().

atomic<> derivative data types are declared as below.
atomic<int> i=5;
atomic_int j=10;
 
memory order
load(), store() and exchange() APIs also accept additional parameter for accepting memory order.  It must be one of the following below.
OrderDescription
memory_order_relaxed         There are no synchronization or ordering constraints imposed on other reads or writes, only this operation's atomicity is guaranteed
memory_order_consume      A load operation with this memory order performs a consume operation on the affected memory location: no reads or writes in the current thread dependent on the value currently loaded can be reordered before this load. Writes to data-dependent variables in other threads that release the same atomic variable are visible in the current thread. On most platforms, this affects compiler optimizations only
memory_order_acquire       A load operation with this memory order performs the acquire operation on the affected memory location: no reads or writes in the current thread can be reordered before this load. All writes in other threads that release the same atomic variable are visible in the current thread
memory_order_release     A store operation with this memory order performs the release operation: no reads or writes in the current thread can be reordered after this store. All writes in the current thread are visible in other threads that acquire the same atomic variable and writes that carry a dependency into the atomic variable become visible in other threads that consume the same atomic.
memory_order_acq_relA read-modify-write operation with this memory order is both an acquire operation and a release operation. No memory reads or writes in the current thread can be reordered before the load, nor after the store. All writes in other threads that release the same atomic variable are visible before the modification and the modification is visible in other threads that acquire the same atomic variable.
memory_order_seq_cst                                                                                                                                      A load operation with this memory order performs an acquire operation, a store performs a release operation, and read-modify-write performs both an acquire operation and a release operation, plus a single total order exists in which all threads observe all modifications in the same order.

methods
The following are general methods available to instances.
NameDescription
bool is_lock_free()Checks if the atomic object is lock-free
void store
(T desired, memory_order order = memory_order_seq_cst )
Atomically replaces the value of the atomic object with a non-atomic argument. Memory is affected according to the value of order which can be any of memory_order_relaxed, memory_order_release, memory_order_seq_cst
T load
(memory_order order = memory_order_seq_cst )
Atomically obtains the value of the atomic object. Memory is affected according to the value of order which can be any of memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_seq_cst.
T exchange
( T desired, memory_order order = memory_order_seq_cst )
Atomically replaces the value of the atomic object and obtains the value held previously.
The entire operation is atomic (an atomic read-modify-write operation): the value is not affected by other threads between the instant its value is read (to be returned) and the moment it is modified by this function.
Memory is affected according to the value of order which can be any of memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel, memory_order_seq_cst.
  1. bool compare_exchange_weak
     (T& expected, T desired,
    memory_order success,  memory_order failure) 
  2. bool compare_exchange_weak
     (T& expected, T desired,
    memory_order order =   memory_order_seq_cst
Compares the contents of the atomic object's contained value with expected:
- if true, it replaces the contained value with val (like store).
- if false, it replaces expected with the contained value .

The function always accesses the contained value to read it, and -if the comparison is true- it then also replaces it. But the entire operation is atomic: the value cannot be modified by other threads between the instant its value is read and the moment it is replaced.

The memory order used in the 1st overload depends on the result of the comparison: if true, it uses success; if false, it uses failure.
In the 2nd overload, memory is affected according to the value of order.

Unlike compare_exchange_strong, this weak version is allowed to fail spuriously by returning false even when expected indeed compares equal to the contained object. This may be acceptable behavior for certain looping algorithms, and may lead to significantly better performance on some platforms. On these spurious failures, the function returns false while not modifying expected.

For non-looping algorithms, compare_exchange_strong is generally preferred.
  1. bool compare_exchange_strong
    (T& expected, T desired,
    memory_order success, memory_order failure ) 
  2. bool compare_exchange_strong
    (T& expected, T desired,
    memory_order order = memory_order_seq_cst
Behave same as compare_exchange_weak().

Unlike compare_exchange_weak(), this strong version is required to always return true when expected indeed compares equal to the contained object, not allowing spurious failures. However, on certain platforms, and for certain algorithms that check this in a loop, compare_exchange_weak may lead to significantly better performance.

The following methods are defined for atomic class specialization for pointers.
NameDescription

T fetch_add
(ptrdiff_t val, memory_order sync = memory_order_seq_cst
Similar to += operation. Atomically adds val to the contained value and returns the previous value  immediately before the operation.
Memory is affected according to the value of sync.
T fetch_sub
(ptrdiff_t val, memory_order sync = memory_order_seq_cst
Similar to -= operation. Atomically subtracts val to the contained value and returns the previous value  immediately before the operation.
Memory is affected according to the value of sync.
T operator+= (ptrdiff_t val) Same as fecth_add.
T operator-= (ptrdiff_t val) Same as fecth_sub.
T operator++() 
operator++ (int)
Same as pre and post increment operations for integral types.
T operator--() 
T operator-- (int)
Same as pre and post decrement operations for integral types.

The following methods are defined for atomic class specialization for floating  types.
NameDescription
T fetch_add
(T val, memory_order sync = memory_order_seq_cst)
Similar to += operation. Atomically adds val to the contained value and returns the previous value  immediately before the operation.
Memory is affected according to the value of sync.
T fetch_sub
(T val, memory_order sync = memory_order_seq_cst)
Similar to -= operation. Atomically subtracts val to the contained value and returns the previous value  immediately before the operation.
Memory is affected according to the value of sync.

The following methods are defined for atomic class specialization for integral types.
NameDescription
T fetch_add
(T val, memory_order sync = memory_order_seq_cst)
Similar to += operation. Atomically adds val to the contained value and returns the previous value  immediately before the operation.
Memory is affected according to the value of sync.
T fetch_sub
(T val, memory_order sync = memory_order_seq_cst)
Similar to -= operation. Atomically subtracts val to the contained value and returns the previous value  immediately before the operation.
Memory is affected according to the value of sync.
T fetch_and
(T val, memory_order  sync = memory_order_seq_cst
Atomically performs bitwise AND between the argument and the value of the atomic object and obtains the value held previously.
Memory is affected according to the value of sync.
T fetch_or
(T val, memory_order sync = memory_order_seq_cst
Atomically performs bitwise OR between the argument and the value of the atomic object and obtains the value held previously.
Memory is affected according to the value of sync.
T fetch_xor
(T val, memory_order sync = memory_order_seq_cst
atomically performs bitwise XOR between the argument and the value of the atomic object and obtains the value held previously.
Memory is affected according to the value of sync.
T operator++() 
T operator++ (int) 
  1. Pre Increments the value of the contained value and returns the resulting contained value 
  2. Post Increments the value of the contained value and returns the previous value it had immediately before the operation.
The entire operation is atomic: the value cannot be modified between the instant its value is read and the moment it is modified by this function.
T operator--() 
T operator-- (int)
  1. Pre decrements the value of the contained value and returns the resulting contained value 
  2. Post decrements the value of the contained value and returns the previous value it had immediately before the operation.
The entire operation is atomic: the value cannot be modified between the instant its value is read and the moment it is modified by this function.
T operator+= (T val) Same as fecth_add.
T operator-= (T val) Same as fecth_sub.
T operator&= (T val)Same as operator and.
T operator|= (T val)Same as operator or.
T operator^= (T val)Same as operator xor.

This  example 20  demonstrates the functionality of the atomics<> as seen in its console output.  
It defines a critical_section based on atomic<bool> that can be used to synchronize threads like a mutex. It also defines a stack that can be used by multiple threads  for access and updates.

This example 21  demonstrates the functionality of the atomic<> as seen in its console output. 
The main thread spawns two worker threads to print numbers serially. Synchronization is provided by a atomic<int>.

atomic_flag 
atomic_flag is an atomic boolean type. Unlike all specializations of  atomic<>, it is guaranteed to be lock-free and also it does not provide load() or store() operations.
constructors
NameDescription
  1. atomic_flag()
  2. atomic_flag(val
  1. Default constructor
  2. Initialization constructor

atomic_flag  supports following operations.
NameDescription
void clear
(memory_order order =              memory_order_seq_cst )
Atomically sets flag to false. Memory is affected according to the value of order which can be any of memory_order_relaxedmemory_order_releasememory_order_seq_cst
bool test_and_set
(memory_order order = memory_order_seq_cst)
Atomically sets the flag to true and obtains its previous value. Memory is affected according to the value of order which can be any of memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel, memory_order_seq_cst.

This example 22  demonstrates the functionality of the atomic_flag as seen in its console output. 
It defines a critical_section based on atomic<bool> that can be used to synchronize threads like a mutex. It also defines a stack that can be used by multiple threads  for access and updates.

No comments:

Post a Comment