| Document #: | P2160R0 |
| Date: | 2020-05-13 |
| Project: | Programming Language C++ |
| Audience: |
LWG |
| Reply-to: |
Tim Song <t.canens.cpp@gmail.com> |
This paper provides wording to clean up 32.5.4 [thread.lock] and resolve [LWG2363].
The original complaint of [LWG2363] (a nonexistent SharedTimedMutex named requirement) has since been editorially resolved, and since these requirements are only intended to be used for standard library types rather than user code, I don’t see a need to promote them to CamelCased named requirements as in the current PR.
However, the previous drafting did reveal additional issues:
shared_lock<Mutex> requires Mutex to meet the “shared mutex requirements 32.5.3.5 [thread.sharedtimedmutex.requirements]”; this is a mismatch and also seemingly makes shared_lock<shared_mutex> undefined behavior outright even if the user doesn’t call the timed wait functions.shared_lock appears to disallow user-defined shared mutex types, because it references our internal requirements. This is a clear defect.The wording below introduces new Cpp17SharedLockable and Cpp17SharedTimedLockable named requirements. I decided to add the Cpp17 prefix because they deal with components added in C++17. Because the existing Cpp17MeowLockable requirements are very explicit that they do not deal with the nature of any lock ownership, the same is true for the new requirements. As far as the lockable requirements are concerned, “shared” and “non-shared” locks are distinguished solely by the functions used to acquire them.
As discussed above, the wording removes most explicit preconditions on lock constructors that are of the form “the calling thread does not own the mutex”; when instantiated with types that do not support recursive locking (and consider such attempts undefined behavior), this precondition is implicitly imposed by the call to the locking functions the constructors are specified to perform.
The adopt_lock_t overloads retain their precondition that the lock has been acquired, but re-expressed in lockable terms. This is not strictly necessary - failure to lock results in a precondition violation when the unlocking occurs - but appears to be harmless and potentially permits early diagnosis.
shared_lock does not appear to have a reason to care whether the lockable also allows non-shared locking, and those requirements have been removed from this version. Do we want to reinstate them?This wording is relative to [N4861].
3 The standard library templates
unique_lock(32.5.4.3 [thread.lock.unique]),shared_lock(32.5.4.4 [thread.lock.shared]),scoped_lock(32.5.4.2 [thread.lock.scoped]),lock_guard(32.5.4.1 [thread.lock.guard]),lock,try_lock(32.5.5 [thread.lock.algorithm]), andcondition_variable_any(32.6.4 [thread.condition.condvarany]) all operate on user-supplied lockable objects. The Cpp17BasicLockable requirements, the Cpp17Lockable requirements, and the Cpp17TimedLockable requirements, the Cpp17SharedLockable requirements, and the Cpp17SharedTimedLockable requirements list the requirements imposed by these library types in order to acquire or release ownership of a lock by a given execution agent. [ Note: The nature of any lock ownership and any synchronization it entails are not part of these requirements. — end note ]
4 A lock on an object
mis said to be a non-shared lock if it is acquired by a call tolock,try_lock,try_lock_for, ortry_lock_untilonm, and a shared lock if it is acquired by a call tolock_shared,try_lock_shared,try_lock_shared_for, ortry_lock_shared_untilonm. [ Note: Only the method of lock acquisition is considered; the nature of any lock ownership is not part of these definitions. — end note ]
?.?.?.? Cpp17SharedLockable requirements [thread.req.lockable.shared]
1 A type
Lmeets the Cpp17SharedLockable requirements if the following expressions are well-formed, have the specified semantics, and the expressionm.try_lock_shared()has typebool(mdenotes a value of typeL):2 Effects: Blocks until a lock can be acquired for the current execution agent. If an exception is thrown then a lock shall not have been acquired for the current execution agent.
3 Effects: Attempts to acquire a lock for the current execution agent without blocking. If an exception is thrown then a lock shall not have been acquired for the current execution agent.
4 Returns:
trueif the lock was acquired,falseotherwise.5 Preconditions: The current execution agent holds a shared lock on
m.6 Effects: Releases a shared lock on
mheld by the current execution agent.7 Throws: Nothing.
?.?.?.? Cpp17SharedTimedLockable requirements [thread.req.lockable.shared.timed]
1 A type
Lmeets the Cpp17SharedTimedLockable requirements if it meets the Cpp17SharedLockable requirements, and the following expressions are well-formed, have typebool, and have the specified semantics (mdenotes a value of typeL,rel_timedenotes a value of a specialization ofduration, andabs_timedenotes a value of a specialization oftime_point):2 Effects: Attempts to acquire a lock for the current execution agent within the relative timeout (32.2.4 [thread.req.timing]) specified by
rel_time. The function will not return within the timeout specified byrel_timeunless it has obtained a lock onmfor the current execution agent. If an exception is thrown then a lock has not been acquired for the current execution agent.3 Returns:
trueif the lock was acquired,falseotherwise.4 Effects: Attempts to acquire a lock for the current execution agent before the absolute timeout (32.2.4 [thread.req.timing]) specified by
abs_time. The function will not return before the timeout specified byabs_timeunless it has obtained a lock onmfor the current execution agent. If an exception is thrown then a lock has not been acquired for the current execution agent.5 Returns:
trueif the lock was acquired,falseotherwise.
2 [ Note: The mutex types meet the Cpp17Lockable requirements (32.2.5.3 [thread.req.lockable.req]). — end note ]
2 [ Note: The timed mutex types meet the Cpp17TimedLockable requirements (32.2.5.4 [thread.req.lockable.timed]). — end note ]
? [ Note: The shared mutex types meet the Cpp17SharedLockable requirements (?.?.?.? [thread.req.lockable.shared]). — end note ]
? [ Note: The shared timed mutex types meet the Cpp17SharedTimedLockable requirements (?.?.?.? [thread.req.lockable.shared.timed]). — end note ]
2 Preconditions: If
mutex_typeis not a recursive mutex, the calling thread does not own the mutexm.3 Effects: Initializes
pmwithm. Callsm.lock().4 Preconditions: The calling thread owns the mutex
mholds a non-shared lock onm.5 Effects: Initializes
pmwithm.6 Throws: Nothing.
7 Effects: As if by Equivalent to:
pm.unlock().
2 Preconditions: If a
MutexTypestype is not a recursive mutex, the calling thread does not own the corresponding mutex element ofm.3 Effects: Initializes
pmwithtie(m...). Then ifsizeof...(MutexTypes)is0, no effects. Otherwise ifsizeof...(MutexTypes)is1, thenm.lock(). Otherwise,lock(m...).4 Preconditions: The calling thread owns all the mutexes in
mholds a non-shared lock on each element ofm.5 Effects: Initializes
pmwithtie(m...).6 Throws: Nothing.
7 Effects: For all
iin[0, sizeof...(MutexTypes)),get<i>(pm).unlock().
2 Preconditions: If
mutex_typeis not a recursive mutex the calling thread does not own the mutex.3 Effects: Calls
m.lock().4 Postconditions:
pm == addressof(m)andowns == true.5 Postconditions:
pm == addressof(m)andowns == false.6 Preconditions: The supplied
Mutextype meets the Cpp17Lockable requirements (32.2.5.3 [thread.req.lockable.req]). Ifmutex_typeis not a recursive mutex the calling thread does not own the mutex.7 Effects: Calls
m.try_lock().8 Postconditions:
pm == addressof(m)andowns == res, whereresis the value returned by the call tom.try_lock().9 Preconditions: The calling thread owns the mutex holds a non-shared lock on
m.10 Postconditions:
pm == addressof(m)andowns == true.11 Throws: Nothing.
template<class Clock, class Duration> unique_lock(mutex_type& m, const chrono::time_point<Clock, Duration>& abs_time);12 Preconditions: If
mutex_typeis not a recursive mutex the calling thread does not own the mutex. The suppliedMutextype meets the Cpp17TimedLockable requirements (32.2.5.4 [thread.req.lockable.timed]).13 Effects: Calls
m.try_lock_until(abs_time).14 Postconditions:
pm == addressof(m)andowns == res, whereresis the value returned by the call tom.try_lock_until(abs_time).template<class Rep, class Period> unique_lock(mutex_type& m, const chrono::duration<Rep, Period>& rel_time);15 Preconditions: If
mutex_typeis not a recursive mutex the calling thread does not own the mutex. The suppliedMutextype meets the Cpp17TimedLockable requirements (32.2.5.4 [thread.req.lockable.timed]).16 Effects: Calls
m.try_lock_for(rel_time).17 Postconditions:
pm == addressof(m)andowns == res, whereresis the value returned by the call tom.try_lock_for(rel_time).
1 An object of type
shared_lockcontrols the shared ownership of a lockable object within a scope. Shared ownership of the lockable object may be acquired at construction or after construction, and may be transferred, after acquisition, to anothershared_lockobject. Objects of typeshared_lockare not copyable but are movable. The behavior of a program is undefined if the contained pointerpmis not null and the lockable object pointed to bypmdoes not exist for the entire remaining lifetime (6.7.3 [basic.life]) of theshared_lockobject. The suppliedMutextype shall meet the shared mutex Cpp17SharedLockable requirements (32.5.3.5 [thread.sharedtimedmutex.requirements] ?.?.?.? [thread.req.lockable.shared]).2 [ Note:
shared_lock<Mutex>meets the Cpp17Lockable requirements (32.2.5.3 [thread.req.lockable.req]). IfMutexmeets the Cpp17SharedTimedLockable requirements (?.?.?.? [thread.req.lockable.shared.timed]),shared_lock<Mutex>also meets the Cpp17TimedLockable requirements (32.2.5.4 [thread.req.lockable.timed]). — end note ]
2 Preconditions: The calling thread does not own the mutex for any ownership mode.
3 Effects: Calls
m.lock_shared().4 Postconditions:
pm == addressof(m)andowns == true.5 Postconditions:
pm == addressof(m)andowns == false.6 Preconditions: The calling thread does not own the mutex for any ownership mode.
7 Effects: Calls
m.try_lock_shared().8 Postconditions:
pm == addressof(m)andowns == reswhereresis the value returned by the call tom.try_lock_shared().9 Preconditions: The calling thread has shared ownership of the mutex. holds a shared lock on
m.10 Postconditions:
pm == addressof(m)andowns == true.template<class Clock, class Duration> shared_lock(mutex_type& m, const chrono::time_point<Clock, Duration>& abs_time);11 Preconditions: The calling thread does not own the mutex for any ownership mode.
Mutexmeets the Cpp17SharedTimedLockable requirements (?.?.?.? [thread.req.lockable.shared.timed]).12 Effects: Calls
m.try_lock_shared_until(abs_time).13 Postconditions:
pm == addressof(m)andowns == reswhereresis the value returned by the call tom.try_lock_shared_until(abs_time).template<class Rep, class Period> shared_lock(mutex_type& m, const chrono::duration<Rep, Period>& rel_time);14 Preconditions: The calling thread does not own the mutex for any ownership mode.
Mutexmeets the Cpp17SharedTimedLockable requirements (?.?.?.? [thread.req.lockable.shared.timed]).15 Effects: Calls
m.try_lock_shared_for(rel_time).16 Postconditions:
pm == addressof(m)andowns == reswhereresis the value returned by the call tom.try_lock_shared_for(rel_time).
template<class Clock, class Duration> bool try_lock_until(const chrono::time_point<Clock, Duration>& abs_time);9 ¾ Preconditions:
Mutexmeets the Cpp17SharedTimedLockable requirements (?.?.?.? [thread.req.lockable.shared.timed]).10 Effects: As if by
pm->try_lock_shared_until(abs_time).11 Returns: The value returned by the call to
pm->try_lock_shared_until(abs_time).12 Postconditions:
owns == res, whereresis the value returned by the call topm->try_lock_shared_until(abs_time).13 Throws: Any exception thrown by
pm->try_lock_shared_until(abs_time).system_errorwhen an exception is required (32.2.2 [thread.req.exception]).14 Error conditions:
14 ½ Preconditions:
Mutexmeets the Cpp17SharedTimedLockable requirements (?.?.?.? [thread.req.lockable.shared.timed]).15 Effects: As if by
pm->try_lock_shared_for(rel_time).16 Returns: The value returned by the call to
pm->try_lock_shared_for(rel_time).17 Postconditions:
owns == res, whereresis the value returned by the call topm->try_lock_shared_for(rel_time).18 Throws: Any exception thrown by
pm->try_lock_shared_for(rel_time).system_errorwhen an exception is required (32.2.2 [thread.req.exception]).19 Error conditions:
[LWG2363] Richard Smith. Defect in 30.4.1.4.1 [thread.sharedtimedmutex.class].
https://wg21.link/lwg2363
[N4861] Richard Smith, Thomas Koeppe, Jens Maurer, Dawn Perchik. 2020. Working Draft, Standard for Programming Language C++.
https://wg21.link/n4861