---
title: "Timed lock algorithms for multiple lockables"
document: P3832R2
date: 2026-06-15
audience: LEWG/SG1
reply-to:
  - "Ted Lyngmo <ted@lyncon.se>"
---

- **Document number:** P3832R3
- **Date:** 2026-06-15
- **Audience:** LEWG/SG1
- **Project:** ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
- **Reply-to:** Ted Lyngmo [ted@lyncon.se](mailto:ted@lyncon.se)

## Revision History

### R3

Modifications after `2026-06_Brno:SG18` meeting:

- Added feature test macro `__cpp_lib_timed_lock_alg`.
- Added `<mutex>` synopsis declarations for `try_lock_until` and `try_lock_for`.

### R2

Modifications after `P3832R1:SG1:Brno_2026` meeting:

- Rewrote Effects to explicitly describe the algorithm: `try_lock_until` on one lockable, then `try_lock()` on the rest, with unlock-and-retry semantics.
- Added “This function does not rely on timeouts for deadlock avoidance.”
- Changed “An implementation should ensure…” to normative “This function does not consistently return ≥ 0 in the absence of contending mutex acquisitions.”
- Removed `try_lock_for()` from Effects; the algorithm is now specified in terms of `try_lock_until()` and `try_lock()` only.
- Rewrote Returns to: “the index of the lockable that the implementation was attempting to acquire when the time point `abs_time` was reached.”
- Dropped “in `Ls`” from Preconditions to match standard style.

### R1

- Added link to reference implementation.

Modifications after `2025-11_Kona:SG1-P3832R0` meeting:

- Added a note that an implementation should ensure that `try_lock_until()` does not consistently return ≥ 0 in the absence of contending mutex acquisitions.
- Clarified that no call to `try_lock_for()` or `try_lock_until()` is made while holding a lock on any argument.
- Added `Throws` clause to `try_lock_until`.
- Changed “the index of the last lockable for which locking failed” to “the index of the lockable for which `try_lock_until()` or `try_lock_for()` failed due to `abs_time` being reached” in Effects and Returns.

## Abstract

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:

```cpp
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.



## Impact on the Standard

- Pure library extension.
- No changes to the core language.
- Minimal implementation burden: can be implemented using existing lock-style algorithms plus timeout handling.
- ABI impact: introduction of two new function templates in `<mutex>`.

## Design Rationale

- **Free functions**: Consistent with `std::lock` and `std::try_lock`.
- **Parameter pack form (`Ls...`)**: Matches existing multi-lock algorithms; avoids forcing tuple/range usage.
- **Any number of lockables**: Contrary to `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](https://wg21.link/p3833).
- **Deadlock avoidance**: As with `std::lock`, the algorithm is required not to deadlock, but the specific strategy is left unspecified.
- **Exception safety**: If any call to `try_lock()`, `try_lock_for()`, or `try_lock_until()` throws, all previously locked mutexes are released via `unlock()`.
- **Timeout semantics**: Mirrors `try_lock_for()` and `try_lock_until()` in *TimedLockable*.
- **Return value**: `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()`.

## Proposed Wording

The following changes are relative to N5046.

### Header <version> synopsis [version.syn]

Add to [version.syn]:

```cpp
#define __cpp_lib_timed_lock_alg  20XXXXL // also in <mutex>
```

### Header <mutex> synopsis [thread.mutex.syn]

Add to [thread.mutex.syn], after the declarations of `lock` and `try_lock`:

```cpp
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);
```

### Generic locking algorithms [thread.lock.algorithm]

In 32.6.6, after point 5, add:

```cpp
template <class Clock, class Duration, class... Ls>
int try_lock_until(const chrono::time_point<Clock, Duration>& abs_time, Ls&... ls);
```

****6****
***Preconditions*:**
: Each template
parameter type meets the *Cpp17TimedLockable* requirements.

****7****
***Effects*:**
: Calls
`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.
[*Note 1*: The lockable `l` chosen may be
the same or different for multiple executions of the loop. — *end
note*]

****8****
***Returns*:**
: `-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.

****9****
***Throws*:**
: Any exception thrown by
the lockable `try_lock_until()` or `try_lock()`
functions.

```cpp
template <class Rep, class Period, class... Ls>
int try_lock_for(const chrono::duration<Rep, Period>& rel_time, Ls&... ls);
```

****10****
***Preconditions*:**
: Each template
parameter type meets the *Cpp17TimedLockable* requirements.

****11****
***Effects*:**
: Equivalent
to:
`return try_lock_until(chrono::steady_clock::now() + rel_time, ls...);`

## Example

```cpp
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
}
```

## Implementation Experience

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](https://godbolt.org/z/Ya7Pbq85P)

A reference implementation is available at [github.com/bemanproject/timed_lock_alg](https://github.com/bemanproject/timed_lock_alg).

## Acknowledgments

The std-proposals mailing list:

- Arthur O’Dwyer
- Howard Hinnant

Reference implementation:

- improving `make_pack_of_rotating_index_sequences`: Artyer
- code review: indi

## References

- N5046: Working Draft, Programming Languages — C++ (C++26).
