Friday, February 28, 2025

Synchronization Classes and functions

Overview
These classes provide synchronization between Execution Classes.

Details
The following discusses these new features in detail.

mutex
Provides synchronization to access to a shared resource from multiple threads. The following commonly used methods.

member types
NameDescription
native_handle_typeimplementation-defined

constructors
NameDescription
mutex() Default constructor. The object will be in an unlocked state.

methods
NameDescription
void lock()Locks the mutex on the calling thread. Blocks if the mutex is already locked in a different thread. It causes deadlock if called twice on the same thread.
void unlock()Unlocks the mutex Note that a mutex can be unlocked from a different thread. The counts of lock and unlock should match.
bool try_lock()Tries to lock the mutex, returns if the mutex is not available.
native_handle_type native_handle()Returns the underlying implementation-defined native handle object

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

timed_mutex
Similar to to a mutex. It also extends try_lock() as discussed below.

member types
NameDescription
native_handle_typeimplementation-defined

constructors
NameDescription
timed_mutex() Default constructor. The object will be in an unlocked state.

methods
NameDescription
void lock()Locks the mutex on the calling thread. Blocks if the mutex is already locked in a different thread. It causes deadlock if called twice on the same thread.
void unlock()Unlocks the mutex Note that a mutex can be unlocked from a different thread. The counts of lock and unlock should match.
bool try_lock()Tries to lock the mutex, returns if the mutex is not available.
native_handle_type 
native_handle()
Returns the underlying implementation-defined native handle object
bool try_lock_for
(duration d)
Tries to lock the mutex and waits for a time span.
bool try_lock_until
(time_point tp)
Tries to lock the mutex and waits for a time point.

This example 11  demonstrates the functionality of the timed_mutex as seen in its console outputSynchronization is provided by a timed_mutex.
The main thread owns the mutex, spawns a worker thread, sleeps for 3 seconds and unlocks the mutex. The worker thread waits to own the mutex for 5 seconds, print a message to the console. 

recursive_mutex
The owning thread of a mutex cannot call lock() again on it as it will be hung.  
recursive_mutex is same as mutex, except owning  thread can call lock() more than once. it must also call unlock() equal number of times.

member types
NameDescription
native_handle_typeimplementation-defined

constructors
NameDescription
recursive_mutex() Default constructor. The object will be in an unlocked state.

methods
NameDescription
void lock()Locks the mutex on the calling thread. Blocks if the mutex is already locked in a different thread. It does not cause deadlock if called more than once on the same thread.
void unlock()Unlocks the mutex Note that a mutex can be unlocked from a different thread. The counts of lock and unlock should match.
bool try_lock()Tries to lock the mutex, returns if the mutex is not available.
native_handle_type 
native_handle()
Returns the underlying implementation-defined native handle object

This example 12  demonstrates the functionality of the recursive_mutex as seen in 
its console output. Synchronization is provided by a recursive_mutex.
The program defines 3 methods create_table_withoutdata(), insert_data() and create_table_withdata(). All these methods locks the mutex and releases it after work. 
create_table_withdata(method internally calls insert_data() thus mutex is locked twice and unlocked twice.

recusrisve_timed_mutex
Combines functionalities of recursive_mutex and timed_mutex.

member types
NameDescription
native_handle_typeimplementation-defined

constructors
NameDescription
recursive_timed_mutex() Default constructor. The object will be in an unlocked state.

methods
NameDescription
void lock()Locks the mutex on the calling thread. Blocks if the mutex is already locked in a different thread.  It does not cause deadlock if called twice on the same thread.
void unlock()Unlocks the mutex Note that a mutex can be unlocked from a different thread. The counts of lock and unlock should match.
bool try_lock()Tries to lock the mutex, returns if the mutex is not available.
native_handle_type 
native_handle()
Returns the underlying implementation-defined native handle object
bool try_lock_for
(duration d)
Tries to lock the mutex and waits for a time span.
bool try_lock_until
(time_point tp)
Tries to lock the mutex and waits for a time point.

cv_status

enum class cv_status { no_timeout, timeout };
enum class whose values are returned by the wait_for() and wait_until() members of condition_variable and condition_variable_any.

NameDescription
no_timeout Function returned without time out, possibly due to notification.
timeoutFunction returned due to Time out.

conditional_variable
conditinal_variable removes the need for polling and idling during producer-consumer scenarios.

constructors
NameDescription
conditional_variable() Default constructor. 

In order to work, conditinal_variable require a mutex and unique_lock. Optionally a boolean variable to prevent spurious wake ups.
mutex cvmtx;
condition_variable cv;
bool condtion=false;

The following depicts  typical scenario. However multiple variations can be applied.
One or more  consumer threads waits on a condition_variable with a callable object checking for a condition to be true as below.
void consumer()
{
    unique_lock<mutex> lk(cvmtx);
    cv.wait(lk,[](){return condition;});
}

The producer signals the waiting consumer thread(s)  with notify_one() or notify_all() call as below. 
This will wake up the waiting consumer thread(s) from wait.
void producer()
{
    lock_guard(cvmtx);
    condition = true;
    cv.notify_one();
}

The following lists important apis
NameDescription
void notify_one()notifies one waiting thread
void notify_all()notifies all waiting threads
  1. void wait
    (unique_lock l)
  2. cv_status wait
    (unique_lock l, Predicate pred)
Blocks the current thread until the condition variable is awakened.
To prevent spurious wakeups, the 2nd version is implemented as below.
while (!pred()) wait(lck);
  1. void wait_for
    (unique_lock l, duration d)
  2. cv_status wait
    (unique_lock l, duration d, Predicate pred)
Blocks the current thread until the condition variable is awakened or after the specified timeout duration.
To prevent spurious wakeups, the 2nd version is implemented as below.
while (!pred()) wait_for(lck,d);
  1. void wait_until
    (unique_lock l, time_point tp)
  2. cv_status wait_until
    (unique_lock l, time_point tp, Predicate pred)
Blocks the current thread until the condition variable is awakened or until specified time point has been reached.
To prevent spurious wakeups, the 2nd version is implemented as below.
while (!pred()) wait_until(lck,tp);

This example 18   demonstrates the functionality of the conditinal_variable  as seen in its console output using thread.  Three chat clients, in three threads wait for message from chat server in a conditional variable.

conditional_variable_any
conditinal_variable_any is  similar to conditional_variable except its wait functions can take any lockable type such as shareed_lock as argument. Other than that, they are identical.

constructors
NameDescription
conditional_variable_any() Default constructor. 

In order to work, conditinal_variable_any require a mutex and lockable type such as unique_lock or shared_lock. Optionally a boolean variable to prevent spurious wake ups.
mutex cvmtx;
condition_variable cv;
bool condtion=false;

The following depicts  typical scenario. However multiple variations can be applied.
One or more  consumer threads waits on a conditinal_variable_any with a callable object checking for a condition to be true as below.
void consumer()
{
    unique_lock<mutex> lk(cvmtx);
    cv.wait(lk,[](){return condition;});
}

The producer signals the waiting consumer thread(s)  with notify_one() or notify_all() call as below. 
This will wake up the waiting consumer thread(s) from wait.
void producer()
{
    lock_guard(cvmtx);
    condition = true;
    cv.notify_one();
}

The following lists important apis
NameDescription
void notify_one()notifies one waiting thread
void notify_all()notifies all waiting threads
  1. void wait
    (unique_lock l)
  2. cv_status wait
    (unique_lock l, Predicate pred)
Blocks the current thread until the condition variable is awakened.
To prevent spurious wakeups, the 2nd version is implemented as below.
while (!pred()) wait(lck);
  1. void wait_for
    (unique_lock l, duration d)
  2. cv_status wait
    (unique_lock l, duration d, Predicate pred)
Blocks the current thread until the condition variable is awakened or after the specified timeout duration.
To prevent spurious wakeups, the 2nd version is implemented as below.
while (!pred()) wait_for(lck,d);
  1. void wait_until
    (unique_lock l, time_point tp)
  2. cv_status wait_until
    (unique_lock l, time_point tp, Predicate pred)
Blocks the current thread until the condition variable is awakened or until specified time point has been reached.
To prevent spurious wakeups, the 2nd version is implemented as below.
while (!pred()) wait_until(lck,tp);

This example 18   demonstrates the functionality of the conditinal_variable_any as seen in its console output using thread.  Three chat clients, in three threads wait for message from chat server in a conditional variable.


once_flag
Object of this type are used in call_once function.
struct once_flag 
{
    constexpr once_flag() noexcept;
    once_flag (const once_flag&) = delete;
    once_flag& operator= (const once_flag&) = delete;
}
Using the same object on different calls to call_once in different threads causes a single execution if called concurrently.

call_once()
Sometimes applications needs to initialize global variables or a instance of a class only once before it's accessed by multiple threads. It is usually done using a singleton with a lock. 

The call_once template function provides an alternative.
template< class Callable, class... Args >
void call_once( std::once_flag& flag, Callable&& f, Args&&... args );
flag - an object, for which exactly one function gets executed.
f - Executes the Callable object f exactly once, even if called concurrently from several threads.
args - Arguments to f.

Alternatively once_flag can be used. It involves declaring  a once_flag variable and initializing it using call_once() api as shown below. call_once() accepts a callable object. call_once() can be called from multiple threads but it's called only once. if the call fails due to an exception in one thread, it's tried again in another thread.
int balance=0;
std::once_flag balance_flag;
std::call_once(balance_flag, []()
{ 
	balance=1000;
});

This example 16  demonstrates the functionality of the once_flag  as seen in its console output using thread. 

The program defines three bank accounts sam_acct, rob_acct and steve_acct and tries to transfer money from sam_acct to rob_acct, steve_acct using multiple threads. once_flag  is used to initialize the balances. The first call to call_once() fails because of exception and the second succeeds. Third time it does not get called. 

This example 17  demonstrates the functionality of the once_flag  as seen in its console output using async. The functionality is same as Example 16.

No comments:

Post a Comment