32 Concurrency support library [thread] (original) (raw)
32.6 Mutual exclusion [thread.mutex]
32.6.4 Mutex requirements [thread.mutex.requirements]
32.6.4.1 General [thread.mutex.requirements.general]
A mutex object facilitates protection against data races and allows safe synchronization of data between execution agents.
An execution agent owns a mutex from the time it successfully calls one of the lock functions until it calls unlock.
Mutexes can be either recursive or non-recursive, and can grant simultaneous ownership to one or many execution agents.
Both recursive and non-recursive mutexes are supplied.
32.6.4.2 Mutex types [thread.mutex.requirements.mutex]
32.6.4.2.1 General [thread.mutex.requirements.mutex.general]
The mutex types are the standard library types mutex,recursive_mutex, timed_mutex, recursive_timed_mutex,shared_mutex, and shared_timed_mutex.
They meet the requirements set out in [thread.mutex.requirements.mutex].
In this description, m denotes an object of a mutex type.
If initialization of an object of a mutex type fails, an exception of type system_error is thrown.
The mutex types are neither copyable nor movable.
The error conditions for error codes, if any, reported by member functions of the mutex types are as follows:
- resource_unavailable_try_again — if any native handle type manipulated is not available.
- operation_not_permitted — if the thread does not have the privilege to perform the operation.
- invalid_argument — if any native handle type manipulated as part of mutex construction is incorrect.
The implementation provides lock and unlock operations, as described below.
For purposes of determining the existence of a data race, these behave as atomic operations ([intro.multithread]).
The lock and unlock operations on a single mutex appears to occur in a single total order.
[Note 3:
Construction and destruction of an object of a mutex type need not be thread-safe; other synchronization can be used to ensure that mutex objects are initialized and visible to other threads.
— _end note_]
The expression m.lock() is well-formed and has the following semantics:
Preconditions: If m is of type mutex, timed_mutex,shared_mutex, or shared_timed_mutex, the calling thread does not own the mutex.
Effects: Blocks the calling thread until ownership of the mutex can be obtained for the calling thread.
Postconditions: The calling thread owns the mutex.
Error conditions:
- operation_not_permitted — if the thread does not have the privilege to perform the operation.
- resource_deadlock_would_occur — if the implementation detects that a deadlock would occur.
The expression m.try_lock() is well-formed and has the following semantics:
Preconditions: If m is of type mutex, timed_mutex,shared_mutex, or shared_timed_mutex, the calling thread does not own the mutex.
Effects: Attempts to obtain ownership of the mutex for the calling thread without blocking.
If ownership is not obtained, there is no effect and try_lock()immediately returns.
An implementation may fail to obtain the lock even if it is not held by any other thread.
[Note 4:
This spurious failure is normally uncommon, but allows interesting implementations based on a simple compare and exchange ([atomics]).
— _end note_]
An implementation should ensure that try_lock() does not consistently return falsein the absence of contending mutex acquisitions.
Synchronization: If try_lock() returns true, prior unlock() operations on the same object synchronize with this operation.
[Note 5:
Since lock() does not synchronize with a failed subsequenttry_lock(), the visibility rules are weak enough that little would be known about the state after a failure, even in the absence of spurious failures.
— _end note_]
Returns: true if ownership was obtained, otherwise false.
The expression m.unlock() is well-formed and has the following semantics:
Preconditions: The calling thread owns the mutex.
Effects: Releases the calling thread's ownership of the mutex.
Synchronization: This operation synchronizes with subsequent lock operations that obtain ownership on the same object.
32.6.4.2.2 Class mutex [thread.mutex.class]
namespace std { class mutex { public: constexpr mutex() noexcept;~mutex(); mutex(const mutex&) = delete; mutex& operator=(const mutex&) = delete;void lock();bool try_lock();void unlock();using native_handle_type = implementation-defined; native_handle_type native_handle(); };}
The class mutex provides a non-recursive mutex with exclusive ownership semantics.
If one thread owns a mutex object, attempts by another thread to acquire ownership of that object will fail (for try_lock()) or block (forlock()) until the owning thread has released ownership with a call tounlock().
[Note 1:
After a thread A has called unlock(), releasing a mutex, it is possible for another thread B to lock the same mutex, observe that it is no longer in use, unlock it, and destroy it, before thread A appears to have returned from its unlock call.
Conforming implementations handle such scenarios correctly, as long as thread A does not access the mutex after the unlock call returns.
These cases typically occur when a reference-counted object contains a mutex that is used to protect the reference count.
— _end note_]
The class mutex meets all of the mutex requirements ([thread.mutex.requirements]).
[Note 2:
A program can deadlock if the thread that owns a mutex object callslock() on that object.
If the implementation can detect the deadlock, a resource_deadlock_would_occur error condition might be observed.
— _end note_]
The behavior of a program is undefined if it destroys a mutex object owned by any thread or a thread terminates while owning a mutex object.
32.6.4.2.3 Class recursive_mutex [thread.mutex.recursive]
namespace std { class recursive_mutex { public: recursive_mutex();~recursive_mutex(); recursive_mutex(const recursive_mutex&) = delete; recursive_mutex& operator=(const recursive_mutex&) = delete;void lock();bool try_lock() noexcept;void unlock();using native_handle_type = implementation-defined; native_handle_type native_handle(); };}
The class recursive_mutex provides a recursive mutex with exclusive ownership semantics.
If one thread owns a recursive_mutex object, attempts by another thread to acquire ownership of that object will fail (for try_lock()) or block (for lock()) until the first thread has completely released ownership.
The class recursive_mutex meets all of the mutex requirements ([thread.mutex.requirements]).
A thread that owns a recursive_mutex object may acquire additional levels of ownership by calling lock() or try_lock() on that object.
It is unspecified how many levels of ownership may be acquired by a single thread.
If a thread has already acquired the maximum level of ownership for a recursive_mutexobject, additional calls to try_lock() fail, and additional calls tolock() throw an exception of type system_error.
A thread shall call unlock() once for each level of ownership acquired by calls tolock() and try_lock().
Only when all levels of ownership have been released may ownership be acquired by another thread.
The behavior of a program is undefined if
- it destroys a recursive_mutex object owned by any thread or
- a thread terminates while owning a recursive_mutex object.
32.6.4.3 Timed mutex types [thread.timedmutex.requirements]
32.6.4.3.1 General [thread.timedmutex.requirements.general]
The timed mutex types are the standard library types timed_mutex,recursive_timed_mutex, and shared_timed_mutex.
They meet the requirements set out below.
In this description, m denotes an object of a mutex type,rel_time denotes an object of an instantiation of duration, and abs_time denotes an object of an instantiation of time_point.
The expression m.try_lock_for(rel_time) is well-formed and has the following semantics:
Preconditions: If m is of type timed_mutex orshared_timed_mutex, the calling thread does not own the mutex.
Effects: The function attempts to obtain ownership of the mutex within the relative timeout ([thread.req.timing]) specified by rel_time.
If the time specified by rel_time is less than or equal to rel_time.zero(), the function attempts to obtain ownership without blocking (as if by callingtry_lock()).
The function returns within the timeout specified byrel_time only if it has obtained ownership of the mutex object.
[Note 2:
As with try_lock(), there is no guarantee that ownership will be obtained if the lock is available, but implementations are expected to make a strong effort to do so.
— _end note_]
Synchronization: If try_lock_for() returns true, prior unlock() operations on the same object synchronize with ([intro.multithread]) this operation.
Returns: true if ownership was obtained, otherwise false.
The expression m.try_lock_until(abs_time) is well-formed and has the following semantics:
Preconditions: If m is of type timed_mutex orshared_timed_mutex, the calling thread does not own the mutex.
Effects: The function attempts to obtain ownership of the mutex.
Ifabs_time has already passed, the function attempts to obtain ownership without blocking (as if by calling try_lock()).
The function returns before the absolute timeout ([thread.req.timing]) specified byabs_time only if it has obtained ownership of the mutex object.
[Note 3:
As with try_lock(), there is no guarantee that ownership will be obtained if the lock is available, but implementations are expected to make a strong effort to do so.
— _end note_]
Synchronization: If try_lock_until() returns true, prior unlock()operations on the same object synchronize with ([intro.multithread]) this operation.
Returns: true if ownership was obtained, otherwise false.
32.6.4.3.2 Class timed_mutex [thread.timedmutex.class]
namespace std { class timed_mutex { public: timed_mutex();~timed_mutex(); timed_mutex(const timed_mutex&) = delete; timed_mutex& operator=(const timed_mutex&) = delete;void lock(); bool try_lock();template<class Rep, class Period> bool try_lock_for(const chrono::duration<Rep, Period>& rel_time);template<class Clock, class Duration> bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);void unlock();using native_handle_type = implementation-defined; native_handle_type native_handle(); };}
The class timed_mutex provides a non-recursive mutex with exclusive ownership semantics.
If one thread owns a timed_mutex object, attempts by another thread to acquire ownership of that object will fail (for try_lock()) or block (for lock(), try_lock_for(), and try_lock_until()) until the owning thread has released ownership with a call to unlock() or the call to try_lock_for() or try_lock_until() times out (having failed to obtain ownership).
The class timed_mutex meets all of the timed mutex requirements ([thread.timedmutex.requirements]).
The behavior of a program is undefined if
- it destroys a timed_mutex object owned by any thread,
- a thread that owns a timed_mutex object calls lock(),try_lock(), try_lock_for(), or try_lock_until() on that object, or
- a thread terminates while owning a timed_mutex object.
32.6.4.3.3 Class recursive_timed_mutex [thread.timedmutex.recursive]
namespace std { class recursive_timed_mutex { public: recursive_timed_mutex();~recursive_timed_mutex(); recursive_timed_mutex(const recursive_timed_mutex&) = delete; recursive_timed_mutex& operator=(const recursive_timed_mutex&) = delete;void lock(); bool try_lock() noexcept;template<class Rep, class Period> bool try_lock_for(const chrono::duration<Rep, Period>& rel_time);template<class Clock, class Duration> bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);void unlock();using native_handle_type = implementation-defined; native_handle_type native_handle(); };}
The class recursive_timed_mutex provides a recursive mutex with exclusive ownership semantics.
If one thread owns a recursive_timed_mutex object, attempts by another thread to acquire ownership of that object will fail (fortry_lock()) or block (for lock(), try_lock_for(), andtry_lock_until()) until the owning thread has completely released ownership or the call to try_lock_for() or try_lock_until()times out (having failed to obtain ownership).
The class recursive_timed_mutex meets all of the timed mutex requirements ([thread.timedmutex.requirements]).
A thread that owns a recursive_timed_mutex object may acquire additional levels of ownership by calling lock(), try_lock(),try_lock_for(), or try_lock_until() on that object.
It is unspecified how many levels of ownership may be acquired by a single thread.
If a thread has already acquired the maximum level of ownership for arecursive_timed_mutex object, additional calls to try_lock(),try_lock_for(), or try_lock_until() fail, and additional calls to lock() throw an exception of type system_error.
A thread shall call unlock() once for each level of ownership acquired by calls to lock(), try_lock(), try_lock_for(), andtry_lock_until().
Only when all levels of ownership have been released may ownership of the object be acquired by another thread.
The behavior of a program is undefined if
- it destroys a recursive_timed_mutex object owned by any thread, or
- a thread terminates while owning a recursive_timed_mutex object.
32.6.5 Locks [thread.lock]
32.6.5.1 General [thread.lock.general]
A lock is an object that holds a reference to a lockable object and may unlock the lockable object during the lock's destruction (such as when leaving block scope).
An execution agent may use a lock to aid in managing ownership of a lockable object in an exception safe manner.
A lock is said to own a lockable object if it is currently managing the ownership of that lockable object for an execution agent.
A lock does not manage the lifetime of the lockable object it references.
[Note 1:
Locks are intended to ease the burden of unlocking the lockable object under both normal and exceptional circumstances.
— _end note_]
Some lock constructors take tag types which describe what should be done with the lockable object during the lock's construction.
namespace std { struct defer_lock_t { }; struct try_to_lock_t { }; struct adopt_lock_t { }; inline constexpr defer_lock_t defer_lock { };inline constexpr try_to_lock_t try_to_lock { };inline constexpr adopt_lock_t adopt_lock { };}
32.6.5.2 Class template lock_guard [thread.lock.guard]
namespace std { template<class Mutex> class lock_guard { public: using mutex_type = Mutex;explicit lock_guard(mutex_type& m); lock_guard(mutex_type& m, adopt_lock_t);~lock_guard(); lock_guard(const lock_guard&) = delete; lock_guard& operator=(const lock_guard&) = delete;private: mutex_type& pm; };}
An object of type lock_guard controls the ownership of a lockable object within a scope.
A lock_guard object maintains ownership of a lockable object throughout the lock_guard object's lifetime.
The behavior of a program is undefined if the lockable object referenced bypm does not exist for the entire lifetime of the lock_guardobject.
explicit lock_guard(mutex_type& m);
Effects: Initializes pm with m.
Calls m.lock().
lock_guard(mutex_type& m, adopt_lock_t);
Preconditions: The calling thread holds a non-shared lock on m.
Effects: Initializes pm with m.
Effects: Equivalent to: pm.unlock()
32.6.5.3 Class template scoped_lock [thread.lock.scoped]
namespace std { template<class... MutexTypes> class scoped_lock { public: using mutex_type = see below; explicit scoped_lock(MutexTypes&... m);explicit scoped_lock(adopt_lock_t, MutexTypes&... m);~scoped_lock(); scoped_lock(const scoped_lock&) = delete; scoped_lock& operator=(const scoped_lock&) = delete;private: tuple<MutexTypes&...> pm; };}
An object of type scoped_lock controls the ownership of lockable objects within a scope.
A scoped_lock object maintains ownership of lockable objects throughout the scoped_lock object's lifetime.
The behavior of a program is undefined if the lockable objects referenced bypm do not exist for the entire lifetime of the scoped_lockobject.
- If sizeof...(MutexTypes) is one, let Mutex denote the sole type constituting the pack MutexTypes.
The member typedef-name mutex_typedenotes the same type as Mutex.
explicit scoped_lock(MutexTypes&... m);
Effects: Initializes pm with tie(m...).
Then if sizeof...(MutexTypes) is 0, no effects.
Otherwise if sizeof...(MutexTypes) is 1, then m.lock().
Otherwise, lock(m...).
explicit scoped_lock(adopt_lock_t, MutexTypes&... m);
Preconditions: The calling thread holds a non-shared lock on each element of m.
Effects: Initializes pm with tie(m...).
Effects: For all i in [0, sizeof...(MutexTypes)),get<i>(pm).unlock().
32.6.5.4 Class template unique_lock [thread.lock.unique]
32.6.5.4.1 General [thread.lock.unique.general]
namespace std { template<class Mutex> class unique_lock { public: using mutex_type = Mutex; unique_lock() noexcept;explicit unique_lock(mutex_type& m); unique_lock(mutex_type& m, defer_lock_t) noexcept; unique_lock(mutex_type& m, try_to_lock_t); unique_lock(mutex_type& m, adopt_lock_t);template<class Clock, class Duration> unique_lock(mutex_type& m, const chrono::time_point<Clock, Duration>& abs_time);template<class Rep, class Period> unique_lock(mutex_type& m, const chrono::duration<Rep, Period>& rel_time);~unique_lock(); unique_lock(const unique_lock&) = delete; unique_lock& operator=(const unique_lock&) = delete; unique_lock(unique_lock&& u) noexcept; unique_lock& operator=(unique_lock&& u) noexcept;void lock();bool try_lock();template<class Rep, class Period> bool try_lock_for(const chrono::duration<Rep, Period>& rel_time);template<class Clock, class Duration> bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);void unlock();void swap(unique_lock& u) noexcept; mutex_type* release() noexcept;bool owns_lock() const noexcept;explicit operator bool() const noexcept; mutex_type* mutex() const noexcept;private: mutex_type* pm; bool owns; };}
An object of type unique_lock controls the ownership of a lockable object within a scope.
Ownership of the lockable object may be acquired at construction or after construction, and may be transferred, after acquisition, to another unique_lock object.
Objects of type unique_lock are not copyable but are movable.
The behavior of a program is undefined if the contained pointerpm is not null and the lockable object pointed to by pm does not exist for the entire remaining lifetime ([basic.life]) of the unique_lock object.
32.6.5.4.2 Constructors, destructor, and assignment [thread.lock.unique.cons]
Postconditions: pm == nullptr and owns == false.
explicit unique_lock(mutex_type& m);
Postconditions: pm == addressof(m) and owns == true.
unique_lock(mutex_type& m, defer_lock_t) noexcept;
Postconditions: pm == addressof(m) and owns == false.
unique_lock(mutex_type& m, try_to_lock_t);
Effects: Calls m.try_lock().
Postconditions: pm == addressof(m) and owns == res, where res is the value returned by the call to m.try_lock().
unique_lock(mutex_type& m, adopt_lock_t);
Preconditions: The calling thread holds a non-shared lock on m.
Postconditions: pm == addressof(m) and owns == true.
template<class Clock, class Duration> unique_lock(mutex_type& m, const chrono::time_point<Clock, Duration>& abs_time);
Effects: Calls m.try_lock_until(abs_time).
Postconditions: pm == addressof(m) and owns == res, where res is the value returned by the call to m.try_lock_until(abs_time).
template<class Rep, class Period> unique_lock(mutex_type& m, const chrono::duration<Rep, Period>& rel_time);
Effects: Calls m.try_lock_for(rel_time).
Postconditions: pm == addressof(m) and owns == res, where res is the value returned by the call to m.try_lock_for(rel_time).
unique_lock(unique_lock&& u) noexcept;
Postconditions: pm == u_p.pm and owns == u_p.owns (where u_p is the state of u just prior to this construction), u.pm == 0 and u.owns == false.
unique_lock& operator=(unique_lock&& u) noexcept;
Effects: Equivalent to: unique_lock(std::move(u)).swap(*this)
Effects: If owns calls pm->unlock().
32.6.5.4.3 Locking [thread.lock.unique.locking]
Effects: As if by pm->lock().
Postconditions: owns == true.
Throws: Any exception thrown by pm->lock().
Error conditions:
- operation_not_permitted — if pm is nullptr.
- resource_deadlock_would_occur — if on entry ownsis true.
Effects: As if by pm->try_lock().
Postconditions: owns == res, where res is the value returned bypm->try_lock().
Returns: The value returned by pm->try_lock().
Throws: Any exception thrown by pm->try_lock().
Error conditions:
- operation_not_permitted — if pm is nullptr.
- resource_deadlock_would_occur — if on entry ownsis true.
template<class Clock, class Duration> bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);
Effects: As if by pm->try_lock_until(abs_time).
Postconditions: owns == res, where res is the value returned bypm->try_lock_until(abs_time).
Returns: The value returned by pm->try_lock_until(abs_time).
Throws: Any exception thrown by pm->try_lock_until(abstime).
Error conditions:
- operation_not_permitted — if pm is nullptr.
- resource_deadlock_would_occur — if on entry owns istrue.
template<class Rep, class Period> bool try_lock_for(const chrono::duration<Rep, Period>& rel_time);
Effects: As if by pm->try_lock_for(rel_time).
Postconditions: owns == res, where res is the value returned by pm->try_lock_for(rel_time).
Returns: The value returned by pm->try_lock_for(rel_time).
Throws: Any exception thrown by pm->try_lock_for(rel_time).
Error conditions:
- operation_not_permitted — if pm is nullptr.
- resource_deadlock_would_occur — if on entry owns istrue.
Effects: As if by pm->unlock().
Postconditions: owns == false.
Error conditions:
- operation_not_permitted — if on entry owns is false.
32.6.5.4.4 Modifiers [thread.lock.unique.mod]
void swap(unique_lock& u) noexcept;
Effects: Swaps the data members of *this and u.
mutex_type* release() noexcept;
Postconditions: pm == 0 and owns == false.
Returns: The previous value of pm.
template<class Mutex> void swap(unique_lock<Mutex>& x, unique_lock<Mutex>& y) noexcept;
Effects: As if by x.swap(y).
32.6.5.4.5 Observers [thread.lock.unique.obs]
bool owns_lock() const noexcept;
explicit operator bool() const noexcept;
mutex_type *mutex() const noexcept;
32.6.6 Generic locking algorithms [thread.lock.algorithm]
template<class L1, class L2, class... L3> int try_lock(L1&, L2&, L3&...);
Preconditions: Each template parameter type meets the Cpp17Lockable requirements.
[Note 1:
Theunique_lock class template meets these requirements when suitably instantiated.
— _end note_]
Effects: Calls try_lock() for each argument in order beginning with the first until all arguments have been processed or a call to try_lock() fails, either by returning false or by throwing an exception.
If a call totry_lock() fails, unlock() is called for all prior arguments with no further calls to try_lock().
Returns: -1 if all calls to try_lock() returned true, otherwise a zero-based index value that indicates the argument for which try_lock()returned false.
template<class L1, class L2, class... L3> void lock(L1&, L2&, L3&...);
Preconditions: Each template parameter type meets the Cpp17Lockable requirements.
[Note 2:
Theunique_lock class template meets these requirements when suitably instantiated.
— _end note_]
Effects: All arguments are locked via a sequence of calls to lock(),try_lock(), or unlock() on each argument.
The sequence of calls does not result in deadlock, but is otherwise unspecified.
[Note 3:
A deadlock avoidance algorithm such as try-and-back-off can be used, but the specific algorithm is not specified to avoid over-constraining implementations.
— _end note_]
If a call tolock() or try_lock() throws an exception, unlock() is called for any argument that had been locked by a call to lock() ortry_lock().
32.6.7 Call once [thread.once]
32.6.7.1 Struct once_flag [thread.once.onceflag]
namespace std { struct once_flag { constexpr once_flag() noexcept; once_flag(const once_flag&) = delete; once_flag& operator=(const once_flag&) = delete;};}
The class once_flag is an opaque data structure that call_once uses to initialize data without causing a data race or deadlock.
constexpr once_flag() noexcept;
Synchronization: The construction of a once_flag object is not synchronized.
Postconditions: The object's internal state is set to indicate to an invocation ofcall_once with the object as its initial argument that no function has been called.
32.6.7.2 Function call_once [thread.once.callonce]
template<class Callable, class... Args> void call_once(once_flag& flag, Callable&& func, Args&&... args);
Mandates: is_invocable_v<Callable, Args...> is true.
Effects: An execution of call_once that does not call its func is apassive execution.
An execution of call_once that calls its funcis an active execution.
An active execution evaluates_INVOKE_(std::forward<Callable>(func), std::forward<Args>(args)...) ([func.require]).
If such a call to functhrows an exception the execution is exceptional, otherwise it is returning.
An exceptional execution propagates the exception to the caller ofcall_once.
Among all executions of call_once for any givenonce_flag: at most one is a returning execution; if there is a returning execution, it is the last active execution; and there are passive executions only if there is a returning execution.
[Note 1:
Passive executions allow other threads to reliably observe the results produced by the earlier returning execution.
— _end note_]
Synchronization: For any given once_flag: all active executions occur in a total order; completion of an active execution synchronizes withthe start of the next one in this total order; and the returning execution synchronizes with the return from all passive executions.
[Example 1: void init(); std::once_flag flag;void f() { std::call_once(flag, init);} struct initializer { void operator()();};void g() { static std::once_flag flag2; std::call_once(flag2, initializer());} class information { std::once_flag verified;void verifier();public: void verify() { std::call_once(verified, &information::verifier, *this); } }; — _end example_]