condition_variable wait_for in C++
Asked Answered
C

2

12

I am working with condition_variable on Visual studio 2019. The condition_variable.wait_for() function returns std::cv_status::no_timeout without any notification.

#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>

std::condition_variable cv;
std::mutex mtx;
bool called = false;

void printThread()
{
    std::unique_lock<std::mutex> lck(mtx);
    while (std::cv_status::timeout == cv.wait_for(lck, std::chrono::seconds(1)))
    {
        std::cout << "*";
    }
    std::cout << "thread exits" << std::endl;
}

int main()
{
    std::thread th(printThread);
    th.join();
    std::cout << "program exits" << std::endl;
}

I think the code will never exit and keep printing *, but it exits after printing some *.

Here is the output:

********************************************************************thread exits
program exits

Why does this happen? Is it the so-called "spurious wakeups"?

Chiles answered 20/11, 2020 at 12:15 Comment(5)
@JHBonarius he has infinitive while loop. wait_for should always return std::cv_status::timeout since notification is not triggered.Macedoine
@JHBonarius Are you saying that the condition_variable is taking more than one second to acquire the mutex?Sky
@JHBonarius and as I point out as notify_xx is never called so it has to always timeout.Macedoine
godbolt.org/z/a971GW works for me (timeouts). Here also it timeouts: wandbox.org/permlink/Y2YBuLYegnufDhXFMacedoine
@MarekR It doesn't have to timeout. It can timeout, but it doesn't have to. His code isn't waiting for anything in particular, so it can wakeup unpredictably.Piotrowski
I
12

Yes, it's a "spurious wakeup". This is explained on cppreference.com's reference page for wait_for:

It may also be unblocked spuriously. When unblocked, regardless of the reason, lock is reacquired and wait_for() exits.

Translation: there are gremlins in your computer. They get grumpy, occasionally. And if they do get grumpy, wait_for returns before the requested timeout expires. And when that happens:

Return value

  1. std::cv_status::timeout if the relative timeout specified by rel_time expired, std::cv_status::no_timeout otherwise.

And that seems to be exactly what you're seeing. The C++ standard permits a C++ implementation to return from wait_for prematurely, for arbitrary reasons, and unless you do return from wait_for when the timeout expires, no_timeout is what you get.

You might be wondering why wait_for (and several other similar functions) may decide to throw up their hands and return "spuriously". But that would be a different question...

Idelson answered 20/11, 2020 at 13:21 Comment(6)
And this is why the various flavors of wait() on a condition variable are always called in a loop that checks for an actual change of some significant variable, and repeats the wait if no change has occurred. Timed waits are not a replacement for an appropriate sleep. Waits are intended to wait for something to happen, and correct code has to check that it actually happened. +1.Subterrane
Ok, but how does that explain the behaviour? In his example the while loop only loops if the wait_for returns timeout and exitst otherwise. So how come the return values changes from timeout to something else after a while (making the loop exit and the function end)? Timer overflow?Impetigo
@Impetigo His code seems to assume that a condition variable has two states, signaled and unsignaled, and it stops looping if the condition variable is in the signaled state. But condition variables are stateless -- they don't have any state. It's not a timer overflow or anything else. For code like this to work, the programmer has to implement some way of tracking state, and this code doesn't. So its behavior is undefined.Piotrowski
Thank you, but I still don't fully understand. Because the check is not made on the state of the cv, but on the return value of a specific variant of wait_for, which according to cppref` only has two output possibilities. There must be some state in there, how else would you know of a time out has occurred? And if you cannot trust the return value to be timeout at all times after this timeout, how can you build any reliable application on top of it?Impetigo
Ok, I'm keeping the previous comment as a reference of where my misconception came from. I don't know why (probably because I've never had to use a cv before) but I completly missed the part of the code where a whole new wait_for is triggered on every loop iteration. I thought the same timeout period was checked every iteration. Now I understand where my misunderstanding came from and I understand what's happening.Impetigo
Just to help anyone else: The wait operation has a state, it can timeout or not, but the condition variable itself doesn't have a state. As for how you build something reliable on top of it, you use the return value only to determine whether you timed out or not.Piotrowski
B
-1

As already explained, it is waking up due spurious wakeup. Such thing make the function wait_for completely useless. The solution is to use the wait_until saving the current time before entering the wait loop:

int count = 1;
std::mutex mutex;
std::condition_variable condition_variable;

void wait() {
    std::unique_lock<std::mutex> lock(mutex);
    count--;

    int timeout = 1000; // 1 second
    std::chrono::time_point<std::chrono::system_clock> timenow = 
        std::chrono::system_clock::now();

    while(count < 0) {
        std::cv_status status = condition_variable.wait_until(
            lock, 
            timenow + std::chrono::duration<double,std::ratio<1,1000>>(timeout));

        if ( std::cv_status::timeout == status) {
            count++;
            break;
        }
    }
}
Birchfield answered 23/4, 2021 at 20:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.