std::condit...ny::wait_for(3)

πŸ“¦ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

std::condition_variable_any::wait_for βœ¨πŸ’…

okay so this is from the C++ Standard Libary manual (yes, β€œLibary”, iconic typo energy), page std::condit...ny::wait_for(3). bestie, this one is all about making a thread chill until something happens, and the details matter fr fr. 🫢


πŸŽ€ NAME πŸŽ€

std::condition_variable_any::wait_for β€” std::condition_variable_any::wait_for


🌸 Synopsis 🌸

three overloads, all iconic, with the C++11/C++20 timestamps kept exactly:

Overload 1 (since C++11):

template< class Lock, class Rep, class Period >
std::cv_status wait_for( Lock& lock,
                         const std::chrono::duration<Rep, Period>& rel_time );

Overload 2 (since C++11):

template< class Lock, class Rep, class Period, class Predicate >
bool wait_for( Lock& lock, const std::chrono::duration<Rep, Period>& rel_time,
               Predicate pred );

Overload 3 (since C++20):

template< class Lock, class Rep, class Period, class Predicate >
bool wait_for( Lock& lock, std::stop_token stoken,
               const std::chrono::duration<Rep, Period>& rel_time,
               Predicate pred );

wait_for causes the current thread to block until the condition variable is notified, the given duration has been elapsed, or a spurious wakeup occurs. pred can be optionally provided to detect spurious wakeup. the way that’s the whole vibe 😌

  • 1) Equivalent to return wait_until(lock, std::chrono::steady_clock::now() + rel_time);.
  • 2,3) Waiting for a specific condition to become true, can be used to ignore spurious awakenings.
  • 2) Equivalent to return wait_until(lock, std::chrono::steady_clock::now() + rel_time, std::move(pred));.
  • 3) Registers *this for the duration of this call, to be notified if a stop request is made on stoken's associated stop-state; it is then equivalent to return wait_until(lock, std::move(stoken), std::chrono::steady_clock::now() + rel_time, std::move(pred));.
Right after wait_for returns, lock is locked by the calling thread. If this postcondition cannot be satisfied^[1], calls std::terminate.
^[1] ↑ This can happen if the re-locking of the mutex throws an exception. do not ignore this one, bestie πŸ’€

🫢 Parameters 🫢

  • lock β€” a lock that must be locked by the calling thread
  • stoken β€” a stop token to register interruption for
  • rel_time β€” the maximum duration to wait
  • pred β€” the predicate to check whether the waiting can be completed

🧚 Type requirements 🧚

  • Lock must meet the requirements of BasicLockable.
  • Predicate must meet the requirements of FunctionObject.

πŸ‘‘ Return value πŸ‘‘

  • 1) std::cv_status::timeout if rel_time has been elapsed since the beginning of this call, otherwise std::cv_status::no_timeout.
  • 2,3) The latest result of pred() before returning to the caller.

πŸ’€ Exceptions πŸ’€

  • 1) Timeout-related exceptions.
  • 2,3) Timeout-related exceptions, and any exception thrown by pred.

🌈 Notes 🌈

Even if notified under lock, overload (1) makes no guarantees about the state of the associated predicate when returning due to timeout. subtle but important, okay? πŸŽ€

The effects of notify_one()/notify_all() and each of the three atomic parts of wait()/wait_for()/wait_until() (unlock+wait, wakeup, and lock) take place in a single total order that can be viewed as modification order of an atomic variable: the order is specific to this individual condition variable. This makes it impossible for notify_one() to, for example, be delayed and unblock a thread that started waiting just after the call to notify_one() was made. that’s the whole β€œno cutting the line” rule, periodt ✨


πŸ“ Example πŸ“

run this code, bestie β€” it’s giving three waiters and one signaler:

// Run this code

#include <chrono>
#include <condition_variable>
#include <iostream>
#include <thread>

std::condition_variable_any cv;
std::mutex cv_m; // This mutex is used for three purposes:
                 // 1) to synchronize accesses to i
                 // 2) to synchronize accesses to std::cerr
                 // 3) for the condition variable cv
int i = 0;

void waits()
{
    std::unique_lock<std::mutex> lk(cv_m);
    std::cerr << "Waiting... \n";
    cv.wait(lk, []{ return i == 1; });
    std::cerr << "...finished waiting. i == 1\n";
}

void signals()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    {
        std::lock_guard<std::mutex> lk(cv_m);
        std::cerr << "Notifying...\n";
    }
    cv.notify_all();

    std::this_thread::sleep_for(std::chrono::seconds(1));

    {
        std::lock_guard<std::mutex> lk(cv_m);
        i = 1;
        std::cerr << "Notifying again...\n";
    }
    cv.notify_all();
}

int main()
{
    std::thread t1(waits), t2(waits), t3(waits), t4(signals);
    t1.join();
    t2.join();
    t3.join();
    t4.join();
}

✨ Possible output ✨

Waiting...
Waiting...
Waiting...
Notifying...
Notifying again...
...finished waiting. i == 1
...finished waiting. i == 1
...finished waiting. i == 1

πŸ”₯ Defect reports πŸ”₯

The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

  • DR LWG 2093, applied to C++11 β€” behavior as published: timeout-related exceptions were missing in the specification; correct behavior: mentions these exceptions.
  • DR LWG 2135, applied to C++11 β€” behavior as published: the behavior was unclear if lock.lock() throws an exception; correct behavior: calls std::terminate in this case.

πŸ¦‹ See also πŸ¦‹

  • wait β€” blocks the current thread until the condition variable is awakened (public member function)
  • wait_until β€” blocks the current thread until the condition variable is awakened or until specified time point has been reached (public member function)

πŸ™ˆ Hidden category πŸ™ˆ

  • Pages with unreviewed LWG DR marker

source/version/date sign-off: cppreference.com Β· 2024.06.10 Β· std::condit...ny::wait_for(3) β€” bye bestie, go wait on something gorgeous πŸ’–βœ¨

πŸ“¦ where this page comes from