Modifications after 2025-11_Kona:SG1-P3832R0
meeting:
try_lock_until() does not consistently return ≥ 0 in the
absence of contending mutex acquisitions.try_lock_for() or
try_lock_until() is made while holding a lock on any
argument.Throws clause to
try_lock_until.try_lock_until() or
try_lock_for() failed due to abs_time being
reached” in Effects and Returns.C++11 introduced std::lock and
std::try_lock (and C++17 introduced
std::scoped_lock) to simplify deadlock-free acquisition of
multiple lockables. These algorithms support BasicLockable and
Lockable objects, but there is currently no facility for timed
acquisition of multiple TimedLockable objects.
Users who require timeout-based locking of multiple mutexes must
implement their own deadlock-avoidance algorithm, typically via
try_lock(), unlock(), and retry. This is
error-prone, verbose, and inconsistent with the existing standard
library facilities.
This paper proposes two new algorithms:
template <class Clock, class Duration, class... Ls>
int try_lock_until(const std::chrono::time_point<Clock, Duration>& tp, Ls&... ls);
template <class Rep, class Period, class... Ls>
int try_lock_for(const std::chrono::duration<Rep, Period>& rel_time, Ls&... ls);These extend the std::lock family of functions to timed
lockables, enabling consistent and safe use of multiple timed
mutexes.
<mutex>.std::lock and std::try_lock.Ls...): Matches
existing multi-lock algorithms; avoids forcing tuple/range usage.std::lock / std::try_lock, the proposed
algorithms accept zero or more lockables. The algorithms can then easily
be used when implementing generic function and class templates, like a
combination of unique_lock and scoped_lock, a
unique_multilock (accepting zero or more lockables), which
may be proposed in a later paper.std::lock,
the algorithm is required not to deadlock, but the specific strategy is
left unspecified.try_lock(), try_lock_for(), or
try_lock_until() throws, all previously locked mutexes are
released via unlock().try_lock_for() and try_lock_until() in
TimedLockable.int is selected to hold
the index of the last lockable for which locking failed to mimic
std::try_lock. Since the algorithm may encounter any number
of failures before timing out, it must return the last for which locking
failed. It may be useful if one wants to implement other algorithms that
takes over where the algorithms in this paper has failed. The magic
value -1 is selected to signal success just like in
std::try_lock().The following changes are relative to N5014.
In 32.6.6, Generic locking algorithms [thread.lock.algorithm], after point 5:
template <class Clock, class Duration, class... Ls>
int try_lock_until(const chrono::time_point<Clock, Duration>& abs_time, Ls&... ls);Ls meets the Cpp17TimedLockable
requirements.try_lock_until(), try_lock_for(),
try_lock() or unlock() on each argument. The
sequence of calls does not result in deadlock. No call to
try_lock_for() or try_lock_until() is made
while holding a lock on any argument, but the sequence is otherwise
unspecified. If all locks are acquired before abs_time has
passed, returns -1. If the time point abs_time
is reached before all locks are acquired, releases any locks it holds
and returns the index of the lockable for which
try_lock_until() or try_lock_for() failed due
to abs_time being reached. If a call to
try_lock_until(), try_lock_for() or
try_lock() throws an exception, unlock() is
called on any object locked by this algorithm prior to the exception,
and the exception is rethrown.try_lock_until() does not consistently return ≥ 0 in the
absence of contending mutex acquisitions.-1 if all
locks were obtained, otherwise the index of the lockable for which
try_lock_until() or try_lock_for() failed due
to abs_time being reached.try_lock_until(), try_lock_for(),
or try_lock() functions.template <class Rep, class Period, class... Ls>
int try_lock_for(const chrono::duration<Rep, Period>& rel_time, Ls&... ls);Ls meets the Cpp17TimedLockable
requirements.return try_lock_until(chrono::steady_clock::now() + rel_time, ls...);std::timed_mutex m1, m2;
if (std::try_lock_for(100ms, m1, m2) == -1) {
// success
std::scoped_lock sl(std::adopt_lock, m1, m2);
// ...
} else {
// failed to acquire within timeout
}Existing implementations of std::lock already use a
deadlock-avoidance algorithm. Using the gcc implementation as an
example, instead of locking one with m.lock() and using
std::try_lock() on the rest, the algorithm could start
locking one with m.try_lock_until(tp). Example at Compiler
Explorer
A reference implementation is available at github.com/bemanproject/timed_lock_alg.
The std-proposals mailing list:
Reference implementation:
make_pack_of_rotating_index_sequences:
Artyer