You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
C++ provides native support for multithreading through the <thread>
library, allowing programs to execute tasks concurrently on multiple cores.
A thread is the smallest sequence of programmed instructions that can be
managed independently by the scheduler. C++ uses the std::thread class to
create and manage threads.
std::thread
Explanation
std::thread is a class that represents an individual thread of
execution.
A thread of execution is a sequence of instructions that can be executed
concurrently with other such sequences in multithreading environments,
while sharing a same address space.
An initialized thread object represents an active thread of
execution; Such a thread object is joinable, and has a unique
thread id.
A default-constructed (non-initialized) thread object is not
joinable, and its thread id is common for all non-joinable
threads.
A joinable thread becomes not joinable if moved from, or if either join
or detach are called on them.
A std::thread object cannot be copied because its copy constructor is
deleted.
When some parameters of funName are lvalue references, their
corresponding arguments must be wrapped in std::ref (for non-const
lvalue references) or std::cref (for const lvalue references) when
passing them to std::thread.
This is necessary because std::thread moves its arguments into the thread
function by default, which can break lvalue reference bindings.
Before a std::thread object is destroyed, its join() or detach() method
must be called to avoid terminating the program.
id: Represents the id of a thread (public member class).
Member functions
(constructor): Constructs new thread object (public member function).
(destructor): Destructs the thread object, underlying thread must be joined
or detached (public member function).
operator=: Moves the thread object (public member function).
joinable: Checks whether the thread is joinable, i.e. potentially running
in parallel context (public member function).
get_id: Returns the id of the thread (public member function).
native_handle: Returns the underlying implementation-defined thread handle
(public member function).
hardware_concurrency [static]: Returns the number of concurrent threads
supported by the implementation (public static member function).
join: Waits for the thread to finish its execution (public member
function).
detach: Permits the thread to execute independently from the thread handle
(public member function).
swap: Swaps two thread objects (public member function).
Non-member functions
std::swap( std::thread ): Specializes the std::swap algorithm (function).
See also
std::jthread (C++20): std::thread with support for auto-joining and
cancellation (class).
Notes
A thread starts executing when the std::thread object is defined, not when
the join function is called.
Calling join merely blocks the calling thread, waiting for the thread to
finish and releasing its resources.
A detached thread (a thread that has been detached) will release its
resources either when the calling thread ends or when it finishes executing
itself.
However, it cannot safely access resources of the main thread unless those
resources have been explicitly managed for concurrent access.
Threads automatically release resources when their function completes.
It is not recommended to forcefully terminate a thread using other methods,
as this may cause memory leaks due to unreleased resources.
A thread that is neither joined nor detached will throw an exception when the
program exits.
std::jthread
Explanation
std::jthread is a class that represents a managed thread of execution with
automatic joining upon destruction and optional cooperative
cancellation.
std::jthread executes a sequence of instructions that can run
concurrently with other threads, all sharing the same address space
in a multithreaded environment.
An initialized std::jthread object represents an active thread of
execution that is automatically joined when the object is destroyed,
making it safer and more convenient to manage.
A default-constructed (non-initialized) std::jthread object is not
joinable, and its thread id is set to a default value shared by
all non-joinable threads.
A std::jthread object becomes not joinable if it has been moved from, or
after it completes its execution and is automatically joined.
A std::jthread object cannot be copied because its copy constructor is
deleted.
Cooperative cancellation is supported through a stop_token, which
allows a thread to respond to stop requests and terminate gracefully.
id: Represents the id of a thread (public member class).
Member functions
(constructor): Constructs new thread object (public member function).
(destructor): If the thread is joinable, then a stop is requested and the
thread joins (public member function).
operator=: Moves the jthread object (public member function).
joinable: Checks whether the thread is joinable, i.e. potentially running
in parallel context (public member function).
get_id: Returns the id of the thread (public member function).
native_handle: Returns the underlying implementation-defined thread handle
(public member function).
hardware_concurrency [static]: Returns the number of concurrent threads
supported by the implementation (public static member function).
join: Waits for the thread to finish its execution (public member
function).
detach: Permits the thread to execute independently from the thread handle
(public member function).
swap: Swaps two thread objects (public member function).
get_stop_source: Returns a stop_source object associated with the shared
stop state of the thread (public member function).
get_stop_token: Returns a stop_token associated with the shared stop
state of the thread (public member function).
request_stop: Requests execution stop via the shared stop state of the
thread (public member function).
Non-member functions
std::swap( std::jthread ) (C++20): Specializes the std::swap algorithm
(function).
thread_local
Explanation
The thread_local storage specifier in C++ is used to declare variables
with thread-local storage duration.
Each thread in a program gets its own, unique instance of a
thread_local variable.
This is particularly useful in multithreaded programs where threads need
their own copies of a variable to avoid conflicts.
When a thread is created, thread_local variables behave like normal
variables in a single-threaded program.
For example, a static thread_local variable is created when the thread
starts and is destroyed when the thread terminates, similar to how a normal
variable is created when a program starts and destroyed when the program
ends.
Syntax
thread_local Type var_name;
std::this_thread
Explanation
The std::this_threadnamespace provides utilities for interacting
with the current thread of execution.
It contains functions to retrieve the thread’s ID, pause execution, or
yield to other threads.
The calling thread yields, offering the implementation the opportunity to
reschedule.
This function shall be called when a thread waits for other threads to
advance without blocking.
Syntax
// Declaration syntax.voidyield() noexcept;
std::ref and std::cref
Explanation
std::ref is a utility function that creates a reference wrapper for a
given object.
It is used when you need to pass an argument by reference to a function,
particularly in situations where a function or class expects its argument to
be passed by value (like in std::thread or in certain algorithms that use
copy semantics).
The key purpose of std::ref is to ensure that arguments are passed as
references even when the target function is expecting arguments by value.
std::ref works like a reference, but it is not a true
reference.
Therefore, a function whose parameters are expecting a direct
referencecannot directly receive a std::reference_wrapper.
std::cref works similarly to std::ref, but it creates a reference
wrapper for constant references.
They are commonly used with std::thread and std::async.
Although both use universal references to accept callback function arguments,
these arguments are copied internally for thread safety.
As a result, passing a regular reference directly is invalid, necessitating
the use of std::reference_wrapper when a true reference is required.
Their header file is <functional>.
Syntax
// Its usage syntax.std::ref( var_name );
std::cref( var_name );
Six STD Mutex Classes
std::mutex
A mutex is a lockable object that is designed to signal when critical
sections of code need exclusive access, preventing other threads with
the same protection from executing concurrently and access the same memory
locations.
std::mutex objects provide exclusive ownership and do not support
recursivity (i.e., a thread shall not lock a std::mutex it already
owns) -- see std::recursive_mutex for an alternative class that does.
It is guaranteed to be a standard-layout class.
Its header file is <mutex>.
std::recursive_mutex
std::recursive_mutex is a lockable object, just like std::mutex, but
allows the same thread to acquire multiple levels of ownership over the
std::mutex object (recursivity, recursive calls).
This allows to lock (or try-lock) the mutex object from a thread that is
already locking it, acquiring a new level of ownership over the
std::mutex object: the std::mutexobject will actually remain locked
owning the thread until its member unlock is called as many times as this
level of ownership.
It is guaranteed to be a standard-layout class.
Its header file is <mutex>.
std::timed_mutex
A timed mutex is a time lockable object that is designed to signal when
critical sections of code need exclusive access, just like a regular
mutex, but additionally supporting timed try-lock requests.
As such, a std::timed_mutex has two additional members: try_lock_for and
try_lock_until.
It is guaranteed to be a standard-layout class.
Its header file is <mutex>.
std::recursive_timed_mutex
A recursive timed mutex combines both the features of
std::recursive_mutex and the features of std::timed_mutex into a single
class: it supports both acquiring multiple lock levels by a single thread
and also timed try-lock requests.
It is guaranteed to be a standard-layout class.
Its header file is <mutex>.
std::shared_mutex
The std::shared_mutextemplate class is a synchronization primitive
that can be used to protect shared data from being simultaneously accessed
by multiple threads.
In contrast to other mutex types which facilitate exclusive access, a
std::shared_mutexhas two levels of access:
shared (read-shared-data-only): several threads can share ownership of
the same mutex.
exclusive (read-or-modify-shared-date): only one thread can own the
mutex.
If one thread has acquired the shared lock (through lock_shared,
try_lock_shared), no other thread can acquire the exclusive lock, but can
acquire the shared lock.
If one thread has acquired the exclusive lock (through lock,
try_lock), no other threads can acquire the lock (including the shared).
Only when the exclusive lock has not been acquired by any thread, the shared
lock can be acquired by multiple threads.
Within one thread, only one lock (shared or exclusive) can be acquired at
the same time.
Shared mutexes are especially useful when shared data can be safely
read by any number of threads simultaneously, buta thread may
only write the same data when no other thread is reading or writing at the
same time.
The std::shared_mutex class satisfies all requirements of SharedMutex and
StandardLayoutType.
Its header file is <mutex>.
std::shared_timed_mutex
The std::shared_timed_mutextemplate class is a synchronization
primitive that can be used to protect shared data from being
simultaneously accessed by multiple threads.
In contrast to other mutex types which facilitate exclusive access, a
std::shared_timed_mutexhas two levels of access:
shared (read-shared-data-only): several threads can share ownership of
the same mutex.
exclusive (read-or-modify-shared-date): only one thread can own the
mutex.
Shared mutexes are usually used in situations when multiple readers can
access the same resource at the same time without causing data races, but
only one writer can do so.
In a manner similar to std::timed_mutex, std::shared_timed_mutexprovides the ability to attempt to claim ownership of a
std::shared_timed_mutexwith a timeout via the try_lock_for(),
try_lock_until(), try_lock_shared_for(), try_lock_shared_until() member
functions.
The std::shared_timed_mutex class satisfies all requirements of
SharedTimedMutex and StandardLayoutType.
(constructor): Constructs the mutex (public member function).
(destructor): Destroys the mutex (public member function).
operator=[deleted]: Not copy-assignabl (public member function).
lock: Locks the mutex, blocks if the mutex is not available (public member
function).
try_lock: Tries to lock the mutex, returns if the mutex is not available
(public member function).
try_lock_for (only for std::timed_mutex, std::recursive_timed_mutex and
std::shared_timed_mutex): Tries to lock the mutex, returns if the mutex has
been unavailable for the specified timeout duration (public member function).
try_lock_until (only for std::timed_mutex, std::recursive_timed_mutex
and std::shared_timed_mutex): Tries to lock the mutex, returns if the mutex
has been unavailable until specified time point has been reached (public
member function).
unlock: Unlocks the mutex (public member function).
lock_shared (only for std::shared_mutex and std::shared_timed_mutex):
Locks the mutex for shared ownership, blocks if the mutex is not available
(public member function).
try_lock_shared (only for std::shared_mutex and
std::shared_timed_mutex): Tries to lock the mutex for shared ownership,
returns if the mutex is not available (public member function).
try_lock_shared_for (only for std::shared_timed_mutex): Tries to lock
the mutex for shared ownership, returns if the mutex has been unavailable
for the specified timeout duration (public member function).
try_lock_shared_until (only for std::shared_timed_mutex): Tries to lock
the mutex for shared ownership, returns if the mutex has been unavailable
until specified time point has been reached (public member function).
unlock_shared (only for std::shared_mutex and
std::shared_timed_mutex): Unlocks the mutex (shared ownership) (public
member function).
native_handle: Returns the underlying implementation-defined native handle
object (public member function).
Notes
All lock classes or functions that receive mutex objects implement the
locking or unlocking behavior by calling the mutex object's lock or unlock
member functions.
All mutex objects are passed to them by reference.
All lock objects do not manage the lifetime of the mutex object in any way:
the duration of the mutex object shall extend at least until the destruction
of the lock object that locks it.
A single mutex object can lock multiple shared resources.
However, the best practice is to associate one mutex object with a
single resource for protection.
All threads accessing the same resource must use the same mutex
object to lock it, to prevent undefined behavior.
A mutex can be defined in various scopes depending on the accessibility and
synchronization needs of the shared resource it protects:
Global scope: Suitable for global resources accessed across multiple
functions, providing broad accessibility.
Local scope (function/block): Useful for synchronizing limited code
sections within a function or block, especially if declared as static.
Class member: Protects class-specific resources; a static mutex in a
class provides a class-level lock shared among all instances.
Namespace scope: Limits mutex access to code within a specific
namespace, aiding modularity by avoiding global namespace pollution.
Anonymous namespace (file/module scope): Restricts mutex visibility to
a single file, making it accessible only within that file, which is useful
for file-specific resources.
Smart pointer (std::shared_ptr or std::unique_ptr): Enables dynamic
or conditional mutex management, allowing shared or exclusive ownership of
the mutex among objects.
Mutex Locking
When a thread wants to access a shared resource protected by a mutex, it
calls lock() on the mutex (usually done via std::lock_guard or
std::unique_lock).
If no other thread has locked the mutex, the thread successfully
acquires the lock and can access the shared resource.
If another thread has already locked the mutex, the requesting thread
will block (wait) until the mutex is available.
Mutual Exclusion
Only one thread can own the lock on the mutex at any given time.
Other threads attempting to lock the mutex must wait, ensuring that
only one thread accesses the protected resource at a time.
Unlocking
Once a thread finishes its operations on the shared resource, it
unlocks the mutex, allowing another waiting thread to acquire the
lock.
If the thread used a std::lock_guard or std::unique_lock, the mutex will
be automatically unlocked when the guard or lock object goes out of scope.
Key Points to Consider
Deadlock: If two or more threads try to lock multiple mutexes in
different orders, a deadlock may occur, where each thread waits indefinitely
for the other to release the mutex. Techniques like always locking mutexes in
a specific order or using std::scoped_lock can prevent this.
Performance: Locking and unlocking a mutex are relatively inexpensive
operations, but if many threads frequently compete for the same mutex,
contention can slow down the program.
Recursive Locking: In C++, std::mutex does not allow a thread to lock
the same mutex multiple times without unlocking, as it would lead to a
deadlock with itself. To handle recursive locking by the same thread,
std::recursive_mutex is available.
std::lock_guard
Explanation
std::lock_guard is a template class that manages a mutex object by
keeping it always locked.
On construction, the mutex object is locked by the calling thread,
and on destruction, the mutex is unlocked.
It is the simplest lock, and is specially useful as an object with automatic
duration that lasts until the end of its context.
In this way, it guarantees the mutex object is properly unlocked in case an
exception is thrown.
Mutex: The type of the mutex to lock. The type must meet the
BasicLockable requirements.
Member Types
mutex_type: Mutex.
Member Functions
(constructor): Constructs a lock_guard, optionally locking the given mutex.
Not copy-constructable (public member function).
(destructor): Destructs the lock_guard object, unlocks the underlying mutex
(public member function).
operator=[deleted]: Not copy-assignable (public member function).
std::unique_lock and std::shared_lock
std::unique_lock
std::unique_lock is a template class that manages a mutex object with
unique ownership in both states: locked and unlocked.
On construction (or by move-assigning to it), the object acquires a
mutex object, for whose locking and unlocking operations becomes
responsible.
This class guarantees an unlocked status on destruction (even if not
called explicitly).
Therefore it is especially useful as an object with automatic duration, as it
guarantees the mutex object is properly unlocked in case an exception is
thrown.
Even when working with a shared mutex, std::unique_lock essentially
behaves like a normal lock, blocking other threads from accessing the
shared data.
This is because a std::unique_lock provides exclusive access to the
resource, preventing other threads from acquiring either a shared or
exclusive lock until the unique lock is released.
Its header file is <mutex>.
std::shared_lock
std::shared_lock is a template class that manages a mutex object with
shared ownership.
On construction (or by move-assigning to it), the object acquires a
shared lock on a mutex object, allowing multiple threads to hold the lock
concurrently.
This class guarantees an unlocked status on destruction, releasing the
shared lock when the std::shared_lock object goes out of scope.
It is especially useful for shared access to a resource when multiple
threads need to read from it concurrently, without blocking each other.
However, that std::shared_lock provides only shared access to the
resource, meaning exclusive access is not possible while a shared lock is
held.
While acquiring a shared lock allows other threads to also acquire shared
locks, no thread can acquire an exclusive lock until all shared locks are
released.
The class does not manage the lifetime of the mutex object.
The MutexType used with std::shared_lock must be a shared mutex type.
Mutex: The type of the mutex to lock. The type must meet the
BasicLockable requirements.
Nested Types
mutex_type: Mutex.
Member Functions
(constructor): Constructs a std::unique_lock, optionally locking (i.e.,
taking ownership of) the supplied mutex (public member function).
(destructor): Unlocks (i.e., releases ownership of) the associated mutex, if
owned (public member function).
operator=: Unlocks (i.e., releases ownership of) the mutex, if owned, and
acquires ownership of another (public member function).
lock: Locks (i.e., takes ownership of) the associated mutex (public member
function).
try_lock: Tries to lock (i.e., takes ownership of) the associated mutex
without blocking (public member function).
try_lock_for: Attempts to lock (i.e., takes ownership of) the associated
TimedLockable mutex, returns if the mutex has been unavailable for the
specified time duration (public member function).
try_lock_until: Tries to lock (i.e., takes ownership of) the associated
TimedLockable mutex, returns if the mutex has been unavailable until
specified time point has been reached (public member function).
unlock: Unlocks (i.e., releases ownership of) the associated mutex (public
member function).
swap: Swaps state with another std::unique_lock (public member function).
release: Disassociates the associated mutex without unlocking (i.e.,
releasing ownership of) it (public member function).
mutex: Returns a pointer to the associated mutex (public member function).
owns_lock: Tests whether the lock owns (i.e., has locked) its associated
mutex (public member function).
operator bool: Tests whether the lock owns (i.e., has locked) its
associated mutex (public member function).
The template classstd::scoped_lock is a mutex wrapper that
provides a convenient RAII-style mechanism for owning zero or more
mutexes for the duration of a scoped block.
When a std::scoped_lock object is created, it attempts to take ownership of
the mutexes it is given.
When control leaves the scope in which the std::scoped_lock object was
created, the std::scoped_lock is destructed and the mutexes are released.
If several mutexes are given, deadlock avoidance algorithm is
used as if by std::lock.
MutexTypes: The types of the mutexes to lock. The types must meet the
Lockable requirements unless sizeof...( MutexTypes ) == 1, in which case
the only type must meet BasicLockable.
Member Types
mutex_type (conditionally present): If sizeof...( MutexTypes ) == 1,
member type mutex_type is the same as Mutex, the sole type in
MutexTypes.... Otherwise, there is no member mutex_type.
Member Functions
(constructor): Constructs a std::scoped_lock, optionally locking the given
mutexes (public member function).
(destructor): Destructs the std::scoped_lock object, unlocks the underlying
mutexes (public member function).
operator=[deleted]: Not copy-assignable (public member function).
std::lock and std::try_lock
std::lock
std::locklocks all the objects passed as arguments, blocking the
calling thread if necessary.
The function locks the objects using an unspecified sequence of calls to
their members lock, try_lock and unlock that ensures that all arguments are
locked on return (without producing any deadlocks).
If the function cannot lock all objects (such as because one of its
internal calls threw an exception), the function first unlocks all
objects it successfully locked (if any) before failing.
To prevent deadlock, the order of acquiring multiple locks must be
consistent.
Its header file is <mutex>.
std::try_lock
std::try_lockattempts to lock all the objects passed as arguments using
their std::try_lock member functions (non-blocking).
The function calls the std::try_lock member function for each argument
(first lock1, then lock2, and eventually the others in lockn, in the
same order), until either all calls are successful, or as soon as one of the
calls fails (either by returning false or throwing an exception).
If the function ends because a call fails, unlock is called on all
objects for which the call to std::try_lock was successful, and the
function returns the argument order number of the object whose lock failed.
No further calls are performed for the remaining objects in the argument
list.
If the function locks all mutex objects successfully, it returns -1.
std::atomic is a template class that provides a variety of atomic
operations that guarantee the integrity of data access across multiple
threads.
These operations include read-modify-write operations and
compare-and-swap (CAS) operations.
These operations are implemented using hardware-level atomic instructions,
ensuring that they are indivisible and cannot be interrupted by other
threads.
std::atomicallows you to specify the memory ordering of
operations, which determines how operations on different threads are
synchronized.
This is crucial for ensuring correct behavior in complex concurrent programs.
Many of the operations provided by std::atomic are lock-free, meaning
they don't require explicit locking mechanisms.
Besides, some operations are wait-free, ensuring that a thread will
eventually succeed in its operation, even if other threads are also
contending.
These two features can significantly improve performance in
high-concurrency scenarios.
The std::atomic class is non-copyable.
Its header file is <atomic>.
std::atomic_ref
std::atomic_ref is a template class that provides atomic operations
on non-atomic objects, allowing them to be accessed safely across
multiple threads.
It works by wrapping a reference to an existing object and
providing atomic operations such as read-modify-write and
compare-and-swap (CAS) on the referenced object.
These operations are implemented using hardware-level atomic
instructions, ensuring that they are indivisible and cannot be
interrupted by other threads.
std::atomic_refallows you to specify the memory ordering of
operations, which determines how operations on different threads are
synchronized.
This ensures correct behavior in multi-threaded programs, even when dealing
with non-atomic types.
Unlike std::atomic, which requires atomic types, std::atomic_refworks with regular types like int, double, and user-defined types.
The class does not manage the lifetime of the referenced object, meaning
you must ensure that the referenced object remains valid for the duration of
the atomic operations.
The use of std::atomic_ref can help reduce the overhead of atomic
operations on non-atomic objects and improve performance in
multi-threaded scenarios.
std::atomic_ref is non-copyable, ensuring that the reference is
consistent across multiple threads.
Its header file is <atomic>.
Syntax
// Its usage syntax.
std::atomic< Type > avar_name1;
Type obj_name;
avar_name1 = obj_name;
// Allows temporary objects.
std::atomic< Type > avar_name2( obj_name );
// Its usage syntax.
Type obj_name;
// Constructs an `atomic_ref` object referencing the object `obj_name`.
std::atomic_ref< Type > avar_name1( obj_name );
// Constructs an `atomic_ref` object referencing the object `avar_name1`.
std::atomic_ref< Type > avar_name2( avar_name1 );
value_type: T (regardless of whether specialized or not).
difference_type: value_type (only for atomic< Integral > and
atomic< Floating >(since C++20) specializations), std::ptrdiff_t (only
for std::atomic< U* > specializations).
difference_type is not defined in the primary std::atomic template or in
the partial specializations for std::shared_ptr and std::weak_ptr.
Member Functions
(constructor): Constructs an atomic object (public member function).
operator=: Stores a value into an atomic object (public member function).
is_lock_free: Checks if the atomic object is lock-free (public member
function).
store: Atomically replaces the value of the atomic object with a non-atomic
argument (public member function).
load: Atomically obtains the value of the atomic object (public member
function).
operator T: Loads a value from an atomic object (public member function).
exchange: Atomically replaces the value of the atomic object and obtains
the value held previously (public member function).
compare_exchange_weak, compare_exchange_strong: Atomically compares the
value of the atomic object with non-atomic argument and performs atomic
exchange if equal or atomic load if not (public member function).
wait (C++20): Blocks the thread until notified and the atomic value changes
(public member function).
notify_one (C++20): Notifies at least one thread waiting on the atomic
object (public member function).
notify_all (C++20): Notifies all threads blocked waiting on the atomic
object (public member function).
Constants
is_always_lock_free [static] (C++17): Indicates that the type is always
lock-free (public static member constant).
required_alignment [static] (only for
std::atomic_ref): Indicates the required alignment of an object to be referenced by atomic_ref`
(public static member constant).
Specialized Member Functions
Specialized for Integral, Floating-point (Since C++20) and Pointer Types
fetch_add: Atomically adds the argument to the value stored in the atomic
object and obtains the value held previously (public member function).
fetch_sub: Atomically subtracts the argument from the value stored in the
atomic object and obtains the value held previously (public member function).
operator+=/-=: Adds to or subtracts from the atomic value (public member
function).
Specialized for Integral and Pointer Types Only
fetch_max (C++26): Atomically performs std::max between the argument and
the value of the atomic object and obtains the value held previously (public
member function).
fetch_min (C++26): Atomically performs std::min between the argument and
the value of the atomic object and obtains the value held previously (public
member function).
operator++/++(int)/--/--(int): Increments or decrements the atomic value by
one (public member function).
Specialized for Integral Types Only
fetch_and: Atomically performs bitwise AND between the argument and the
value of the atomic object and obtains the value held previously (public
member function).
fetch_or: Atomically performs bitwise OR between the argument and the value
of the atomic object and obtains the value held previously (public member
function).
fetch_xor: Atomically performs bitwise XOR between the argument and the
value of the atomic object and obtains the value held previously (public
member function).
operator&=/|=/^=: Performs bitwise AND, OR, XOR with the atomic value
(public member function).
All atomic operations, including std::atomic_ref, work only with primitive
type objects or trivially copyable objects.
A trivially copyable object in C++ is one that can be copied (or moved) using
simple memory operations, without needing to invoke any special constructor,
destructor, or copy assignment operator.
In technical terms, an object is trivially copyable if it satisfies the
following criteria:
It has a trivial copy constructor (no custom logic).
It has a trivial copy assignment operator (no custom logic).
It has a trivial destructor (no custom logic).
Its non-static data members are also trivially copyable.
In other words, a trivially copyable object is an object whose members are
primitive type variables or objects containing only primitive type variables.
Additionally, neither the object nor its members explicitly define any member
functions.
std::condition_variable and std::condition_variable_any
std::condition_variable
A condition variable is a class object able to block the calling thread
until notified to resume.
It uses a std::unique_lock (over a mutex) to lock the thread when
one of its wait functions is called.
The thread remains blocked until woken up by another thread that calls
a notification functionon the same std::condition_variable object.
Objects of type std::condition_variable always use
std::unique_lock< std::mutex > to wait: for an alternative that works
with any kind of lockable type, see std::condition_variable_any.
Its header file is <condition_variable>.
std::condition_variable_any
It's the same as std::condition_variable, except that its wait
functions can take any lockable type as argument, while
std::condition_variable objects can only take
std::unique_lock< std::mutex >.
Other than that, they are identical.
However, std::condition_variable_any is slower than
std::condition_variable.
Its header file is <condition_variable>.
Examples
// condition_variable example
#include<iostream>// std::cout
#include<thread>// std::thread
#include<mutex>// std::mutex, std::unique_lock
#include<condition_variable>// std::condition_variable
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
voidprint_id( int id ) {
std::unique_lock< std::mutex > lck( mtx );
while( !ready )
cv.wait( lck );
// The above for-loop is equivalent the following statmenet:// cv.wait( lck, [](){ return ready } );// ...;
std::cout << "thread " << id << '\n';
};
voidgo() {
std::unique_lock< std::mutex > lck( mtx );
ready = true;
cv.notify_all();
};
intmain() {
std::thread threads[10];
// spawn 10 threads:for( int i = 0; i < 10; ++i )
threads[i] = std::thread( print_id, i );
std::cout << "10 threads ready to race...\n";
go(); // go!for( auto& th: threads )
th.join();
return0;
};
(constructor): Constructs the object (public member function).
(destructor): Destructs the object (public member function).
operator=[deleted]: Not copy-assignable (public member function).
notify_one: Notifies one waiting thread (public member function).
notify_all: Notifies all waiting threads (public member function).
wait: Blocks the current thread until the condition variable is awakened
(public member function).
wait_for: Blocks the current thread until the condition variable is
awakened or after the specified timeout duration (public member function).
wait_until: Blocks the current thread until the condition variable is
awakened or until specified time point has been reached (public member
function).
native_handle: Returns the native handle (public member function).
std::notify_all_at_thread_exit
Explanation
When the calling thread exits, all threads waiting on cond are
notified to resume execution.
The function also acquires ownership of the lock on the mutex object
managed by lck, which is stored internally by the function and
unlocked at thread exit (just before notifying all threads), behaving
as if the following was called once all objects with thread storage duration
have been destroyed:
std::async is a template function that calls F (with Args as
arguments) at some point, returning without waiting for the execution of
F to complete.
The value returned by F can be accessed through the future object
returned (by calling its member future::get).
The second version (2) lets the caller select a specific launching
policy, while the first version (1) uses automatic selection, as if
calling (2) with launch::async | launch::deferred as policy.
std::async automatically generates a std::future object, enabling the
caller to obtain the result of an asynchronous operation.
When some parameters of funName are lvalue references, their
corresponding arguments must be wrapped in std::ref (for non-const
lvalue references) or std::cref (for const lvalue references) when
passing them to std::async.
Its header file is <future>.
Syntax
// Declaration syntax.// The first version.template< classF, class... Args >
std::future< /* see below */ > async( F&& f,
Args&&... args ); // (since C++11)
// Declaration syntax.// The second version.template< classF, class... Args >
std::future< /* see below */ > async( std::launch policy,
F&& f,
Args&&... args ); // (since C++11)
policy: Bitmask value, where individual bits control the allowed methods of
execution.
Return Value
std::future: Referring to the shared state created by this call to
std::async.
Launching Policies
Asynchronous (launch::async):
Launches a new thread to call F (as if a thread object is constructed
with F and Args as arguments, and accessing the shared state of the
returned future joins it).
Deferred (launch::deferred):
The call to F is deferred until the shared state of the returned future
is accessed (with wait or get).
At that point, F is called and the function is no longer considered
deferred.
When this call returns, the shared state of the returned future is made
ready.
Automatic (launch::async | launch::deferred):
The function chooses the policy automatically (at some point).
This depends on the system and library implementation, which generally
optimizes for the current availability of concurrency in the system.
Particular library implementations may support additional launching policies,
with the proper label defined in type launch. Combining multiple values is
possible, in which case, the function automatically chooses one among them.
Notes
Pay attention to the lifetimes of variables or objects passed to a function
executed asynchronously. Instead of passing them as references, it may be
better to copy them into the function.
Passing variables or objects as references can lead to issues.
Instead, passing them as pointers might be a better option.
std::future and std::shared_future
std::future
std::future is a template class used to represent the result of an
asynchronous operation.
It provides a mechanism to access the result of a function that runs
concurrently, typically on a separate thread.
This allows a thread to retrieve results at some point in the future,
once the asynchronous task has completed.
std::future is generally used in combination with std::async,
std::promise, or thread-based libraries.
It acts as a placeholder for the result that will eventually become
available.
A std::future object cannot be copied, but it can be moved.
Its header file is <future>.
std::shared_future
The class templatestd::shared_future provides a mechanism to access
the result of asynchronous operations, similar to std::future, except
that multiple threads are allowed to wait for the same shared state.
Unlike std::future, which is only moveable (so only one instance can refer
to any particular asynchronous result), std::shared_future is copyable
and multiple shared future objects may refer to the same shared
state.
Access to the same shared state from multiple threads is safe if each
thread does it through its own copy of a shared_future object.
Its header file is <future>.
Declaration Syntax
std::future< Type > fut_name;
std::shared_future< Type > sfut_name;
Initialization Syntax
// Default constructor.
std::future< Type > fut_name1;
// Move constructor.
std::future< Type > fut_name1 = std::move( fut_name1 );
// Default constructor.
std::shared_future< Type > sfut_name1;
// Copy constructor.
std::shared_future< Type > sfut_name2 = sfut_name1;
// Default constructor.
std::shared_future< Type > sfut_name1;
// Move constructor.
std::shared_future< Type > sfut_name2 = std::move( sfut_name1 );
std::future< Type > fut_name;
// Move constructor.
std::shared_future< Type > sfut_name = std::move( fut_name );
(constructor): Constructs the future object ( Only default and move ) (public
member function).
(destructor): Destructs the future object (public member function).
operator=: Moves the future object ( Only Move ) (public member function).
share (only for std::future): Transfers the shared state from *this to
a shared_future and returns it (public. member function)
get: Returns the result if the result is available. Otherwise,
blocks the calling thread. And only allows to call once.(public
member function).
valid: Checks if the future has a shared state (public member function).
wait: Waits for the result to become available (public member function).
wait_for: Waits for the result, returns if it is not available for the
specified timeout duration (public member function).
wait_until: Waits for the result, returns if it is not available until
specified time point has been reached (public member function).
std::future_status (Returned by wait_for and wait_until Functions)
std::future_status::deferred: The shared state contains a deferred
function, so the result will be computed only when explicitly requested.
std::future_status::ready: The shared state is ready.
std::future_status::timeout: The shared state did not become ready before
specified timeout duration has passed.
Differences Between std::future and std::shared_future
Ownership:
std::future: Sole ownership of the result.
std::shared_future: Shared ownership; multiple instances can access the
same result.
Result retrieval:
std::future: The result can only be retrieved once using get().
std::shared_future: The result can be retrieved multiple times by
different instances.
Copyability:
std::future: Non-copyable but movable.
std::shared_future: Copyable and movable; can be shared across multiple
threads.
Exception handling:
Both: If the asynchronous task throws an exception, it is propagated on the
first call to get().
std::shared_future: The same exception can be propagated multiple times
across all calls to get().
Use case:
std::future: When only one thread needs the result, and the result is
used only once.
std::shared_future: When multiple threads need access to the same result,
or the result needs to be accessed multiple times.
Conversion:
std::future can be converted to std::shared_future using the share()
function.
std::promise
Explanation
A promise is an template class object that can store a value of type
Type to be retrieved by a std::future object (possibly in another
thread), offering a synchronization point.
On construction, promise objects are associated to a new shared state
on which they can store either a value of type Type or an exception
derived from std::exception.
This shared state can be associated to a std::future object by calling
member get_future.
After the call, both objects share the same shared state:
The promise object is the asynchronous provider and is expected to
set a value for the shared state at some point.
The std::future object is an asynchronous return object that can
retrieve the value of the shared state, waiting for it to be ready, if
necessary.
In other words, std::promise is used explicitly when working with
std::thread to pass data or results back to the main thread (or
another thread).
Since std::thread has no built-in mechanism to return results,
std::promise fills this gap:
The std::promise is set by the producer thread.
The std::future associated with the std::promise is used by the
consumer thread to retrieve the result.
The lifetime of the shared state lasts at least until the last object
with which it is associated releases it or is destroyed.
Therefore, it can survive the promise object that obtained it in the first
place if associated also to a future.
Its header file is <future>.
Syntax
std::promise< Type > pro_name;
// Get the `std::future` associated with the promise.
std::future< Type > fut_name = pro_name.get_future();
// Convert it to a shared future.// Method that `std::shared_future` works with `std::promise`.
std::shared_future< Type > sfut_name = fut_name.share();
RetType funcName( ..., std::promise< RetType > pro_name ) {
...;
pro_name.set_value( result ); // Notify futurereturn result;
}
intmain() {
...;
std::promise< RetType > pro_name;
std::future< RetType > pro_fut = pro_name.get_future();
std::thread thread_name( funcName, ..., std::move( pro_name ) );
// future::get() will wait until the future has a valid result and retrieves it.// Calling wait() before get() is not needed// pro_fut.wait(); // wait for result
std::cout << "result=" << pro_fut.get() << '\n';
thread_name.join(); // wait for thread completion
std::future< RetType > async_fut = std::async( funcName, ... );
// Retrieve the result
std::cout << "Result: " << async_fut.get() << std::endl;
}
(constructor): Constructs the promise object (public member function).
(destructor): Destructs the promise object (public member function).
operator=: Assigns the shared state (public member function).
swap: Swaps two promise objects (public member function).
get_future: Returns a future associated with the promised result (public
member function).
set_value: Sets the result to specific value (public member function).
set_value_at_thread_exit: Sets the result to specific value while
delivering the notification only at thread exit (public member function).
set_exception: Sets the result to indicate an exception (public member
function).
set_exception_at_thread_exit: Sets the result to indicate an exception
while delivering the notification only at thread exit (public member
function).
Non-member Functions
std::swap( std::promise ): Specializes the std::swap algorithm (function
template).
Helper Classes
std::uses_allocator< std::promise >: specializes the std::uses_allocator
type trait (class template specialization).
Notes
A game typically loads different objects asynchronously and in parallel using
multiple threads. This is why, at times, players can control their characters
while some objects remain unresponsive.
To the best of my knowledge, most classes related to threads lack a copy
constructor and a copy-assignment operator, except for some classes like
std::shared_future.
To prevent deadlock, the order of acquiring multiple locks must be
consistent.
All static variables are initialized in a thread-safe manner in C++11 and
later, so there's no need to explicitly lock them during initialization.
However, for clarity and code readability, it is still a good practice to use
a mutex when modifying shared static variables across threads after
initialization.