Modifications after 2026-06_Brno:SG18 meeting:
__cpp_lib_timed_lock_alg.<mutex> synopsis declarations for
try_lock_until and try_lock_for.Modifications after P3832R1:SG1:Brno_2026 meeting:
try_lock_until on one lockable, then
try_lock() on the rest, with unlock-and-retry
semantics.try_lock_for() from Effects; the algorithm is
now specified in terms of try_lock_until() and
try_lock() only.abs_time was reached.”Ls” from Preconditions to match standard
style.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
multi_lock (accepting zero or more lockables), which is
proposed in P3833.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 N5046.
<version> synopsis [version.syn]Add to [version.syn]:
#define __cpp_lib_timed_lock_alg 20XXXXL // also in <mutex><mutex> synopsis [thread.mutex.syn]Add to [thread.mutex.syn], after the declarations of
lock and try_lock:
template <class Clock, class Duration, class... Ls>
int try_lock_until(const chrono::time_point<Clock, Duration>& abs_time, Ls&... ls);
template <class Rep, class Period, class... Ls>
int try_lock_for(const chrono::duration<Rep, Period>& rel_time, Ls&... ls);In 32.6.6, after point 5, add:
template <class Clock, class Duration, class... Ls>
int try_lock_until(const chrono::time_point<Clock, Duration>& abs_time, Ls&... ls);try_lock_until(l, abs_time) on an unspecified lockable
l from ls. If this returns true,
then try_lock() is called on the other lockables of
ls in an unspecified order until all have been locked or
one fails. If all calls to try_lock() return
true, the overall operation succeeds. Otherwise,
unlock() is called for all lockables for which
try_lock() or try_lock_until() returned
true, and the above steps are repeated until the time point
abs_time has been reached. The sequence in which locks are
obtained does not result in deadlock. This function does not rely on
timeouts for deadlock avoidance. This function does not consistently
return ≥ 0 in the absence of contending mutex acquisitions. If a call to
try_lock_until() or try_lock() throws an
exception, unlock() is called for any lockable that was
locked by this algorithm prior to the exception, and the exception is
rethrown.l chosen may be
the same or different for multiple executions of the loop. — end
note]-1 if all
locks were obtained, otherwise the index of the lockable that the
implementation was attempting to acquire when the time point
abs_time was reached.try_lock_until() or try_lock()
functions.template <class Rep, class Period, class... Ls>
int try_lock_for(const chrono::duration<Rep, Period>& rel_time, Ls&... ls);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