Friday, February 28, 2025

Synchronization Utility Classes and functions

Overview
These classes provide wrapper for Synchronization classes.

Details
The following discusses these new features in detail.

tag classes
The following empty class tag types can be used in the constructor's parameter list for lock_guard, unique_lock and shared_lock to specify locking strategy.
struct defer_lock_t { explicit defer_lock_t() = default; }
struct try_to_lock_t { explicit try_to_lock_t() = default; }
struct adopt_lock_t { explicit adopt_lock_t() = default; }

The following predefined instances of the above classes  can be passed to the constructors to indicate the type of locking strategy.
defer_lock_t defer_lock {};
try_to_lock_t try_to_lock {};
adopt_lock_t adopt_lock {};

The following table describes the effect of each tag.
NameDescription
defer_lock_tdo not acquire ownership of the mutex
try_to_lock_ttry to acquire ownership of the mutex without blocking
adopt_lock_tassume the calling thread already has ownership of the mutex

lock_guard
lock_guard is a RAII-style object for owning a mutex for the duration of a scoped block.

syntax
// Mutex - the type of the mutex to lock
template< class Mutex >
class lock_guard

member types
NameDescription
mutex_typeTemplate parameter - type of the mutex

constructors
NameDescription
  1. lock_guard
    (mutex_type& m)
  2. lock_guard
    (mutex_type& m, adopt_lock_t tag)
  1. Gets ownership of mutex by calling lock() on it.
  2. Gets  ownership of mutex without calling lock() on it. The tag parameter used to select non-locking version of the constructor.

When a lock_guard object is created, it attempts to take ownership of the mutex it is given. When control leaves the scope in which the lock_guard object was created, the lock_guard is destructed and the mutex is released.

The following code use of the lock_guard. The commented code shows how mutex is locked and unlocked without lock_guard. Whereas a single instantiation of lock_guard removes the need for explicit lock() and unlock() calls.
mutex m;

//{
//	m.lock();
//	cout << "inside"
//	m.unlock();
//}

{
	lock_guard<mutex> lg(m);
	cout << "inside"
}

unique_lock
unique_lock is similar to lock_guard with extended functionality allowing deferred locking, time-constrained attempts at locking, recursive locking, transfer of lock ownership, and use with condition variables.

syntax
// Mutex - the type of the mutex to lock
template< class Mutex >
class unique_lock

member types
NameDescription
mutex_typeTemplate parameter - type of the mutex

constructors
NameDescription
  1. unique_lock()
  2. unique_lock
    (unique_lock&& other) 
  3. unique_lock
    (mutex_type& m)
  4. unique_lock
    (mutex_type& m, defer_lock_t t) 
  5. unique_lock
    (mutex_type& m, try_to_lock_t t)
  6. unique_lock
    (mutex_type& m, adopt_lock_t t)
  7. unique_lock
    (mutex_type& m, duration d)
  8. unique_lock
    (mutex_type& m, time_point tp)
  1. Default constructor
  2. Move constructor
  3. Locks the associated mutex by calling m.lock().
  4. Does not lock the associated mutex.
  5. Tries to lock the associated mutex without blocking by calling m.try_lock(). 
  6. Assumes the calling thread already holds a non-shared lock (i.e., a lock acquired by lock, try_lock, try_lock_for, or try_lock_until) on m. 
  7. Tries to lock the associated mutex by calling m.try_lock_for(timeout_duration). Blocks until specified timeout_duration has elapsed or the lock is acquired, whichever comes first. The mutex_type should be a timed_mutex.
  8. Tries to lock the associated mutex by calling m.try_lock_until(timeout_time). Blocks until specified timeout_time has been reached or the lock is acquired, whichever comes first. The mutex_type should be a timed_mutex.

methods
The following are commonly used methods:
NameDescription
void lock()Tries to lock the associated mutex
bool try_lock()Tries to lock the associated mutex without blocking
bool try_lock_for
(duration d)
Attempts to lock the associated timed_mutex, while waiting for a time span.
bool try_lock_until
(time_point tp)
Attempts to lock the associated timed_mutex, while waiting for a time point.
void unlock()Unlocks  the associated mutex.
mutex_type * release()Disassociates the associated mutex without unlocking it. Returns a pointer to the managed mutex object, releasing ownership over it.
mutex_type * mutex()Returns a pointer to the associated mutex. 
bool owns_lock()Tests whether the lock owns its associated mutex.
operator bool()Tests whether the lock owns its associated mutex

This example 13  demonstrates the functionality of the unique_lock as seen in 
its console output. 

shared_lock
shared_lock is similar to unique_lock except it can be used with condition_variable_any.
The difference between them is that shared_lock is designed to support readers in a read/write lock. It can have multiple threads to acquire the shared lock and reading the same data, but to write to the data,  lock should be used to get permission to write to the data.
syntax
// Mutex - the type of the mutex to lock
template< class Mutex >
class shared_lock
member types
NameDescription
mutex_typeTemplate parameter - type of the mutex

constructors
NameDescription
  1. shared_lock()
  2. shared_lock
    (shared_lock&& other) 
  3. shared_lock
    (mutex_type& m)
  4. shared_lock
    (mutex_type& m, defer_lock_t t) 
  5. shared_lock
    (mutex_type& m, try_to_lock_t t)
  6. shared_lock
    (mutex_type& m, adopt_lock_t t)
  7. shared_lock
    (mutex_type& m, duration d)
  8. shared_lock
    (mutex_type& m, time_point tp)
  1. Default constructor
  2. Move constructor
  3. Locks the associated mutex by calling m.lock().
  4. Does not lock the associated mutex.
  5. Tries to lock the associated mutex without blocking by calling m.try_lock(). 
  6. Assumes the calling thread already holds a non-shared lock (i.e., a lock acquired by lock, try_lock, try_lock_for, or try_lock_until) on m. 
  7. Tries to lock the associated mutex by calling m.try_lock_for(timeout_duration). Blocks until specified timeout_duration has elapsed or the lock is acquired, whichever comes first. The mutex_type should be a timed_mutex.
  8. Tries to lock the associated mutex by calling m.try_lock_until(timeout_time). Blocks until specified timeout_time has been reached or the lock is acquired, whichever comes first. The mutex_type should be a timed_mutex.

methods
The following are commonly used methods:
NameDescription
void lock()Tries to lock the associated mutex
bool try_lock()Tries to lock the associated mutex without blocking
bool try_lock_for
(duration d)
Attempts to lock the associated timed_mutex, while waiting for a time span.
bool try_lock_until
(time_point tp)
Attempts to lock the associated timed_mutex, while waiting for a time point.
void unlock()Unlocks  the associated mutex.
mutex_type * release()Disassociates the associated mutex without unlocking it. Returns a pointer to the managed mutex object, releasing ownership over it.
mutex_type * mutex()Returns a pointer to the associated mutex. 
bool owns_lock()Tests whether the lock owns its associated mutex.
operator bool()Tests whether the lock owns its associated mutex

This example 13  demonstrates the functionality of the shared_lock as seen in 
its console output. 

lock()
lock is a template function that can be used to lock multiple lockable objects.
syntax
//lockables such as mutex  
template< class Lockable, class Lockable2, class... LockableN >
void lock( Lockable& lock, Lockable2& lock2, LockableN&... lockn )

lock enables locking multiple mutexes without deadlocking. When the lock succeeds the thread owns the mutexes. After locking, mutexes must be also unlocked after use. 
This can be done by using adoption_lock  as below
mutex m, m2;
 
lock(m, m2);
unique_lock<mutex> lk(m, adopt_lock);
unique_lockmutex> lk2(m2, adopt_lock);

or by using defer_lock as below. Notice the difference in the arguments type passed to lock() call.
mutex m, m2;
 
unique_lock<mutex> lk(m, defer_lock);
unique_lock<mutex> lk2(m2, defer_lock);
lock(lk, lk2);

This example 14  demonstrates the functionality of the lock as seen in its console output. Synchronization is provided by a mutex and lock.

A restaurant has 2 tables(T, T2) and 2 waiters(W, W2) with a service time of 3 seconds. Initially the main thread spawns four threads assigning waiters and tables. i.e., TW,  TW2, T2W, T2W2. The tables and waiters are protected by a mutex. The assignment algorithm waits on both table and waiter mutexes to make assignments.

This example 15  demonstrates the functionality of the unique_lock as seen in its console output. Synchronization is provided by a  unique_lock  and mutex.
The program tries to transfer money from sam_acct to rob_acct, steve_acct.

No comments:

Post a Comment