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"?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
lfybt
  • 123
  • 1
  • 5
  • @JHBonarius he has infinitive `while` loop. `wait_for` should always return `std::cv_status::timeout` since notification is not triggered. – Marek R Nov 20 '20 at 12:24
  • @JHBonarius Are you saying that the condition_variable is taking more than one second to acquire the mutex? – Harry Nov 20 '20 at 12:34
  • @JHBonarius and as I point out as `notify_xx` is never called so it has to always timeout. – Marek R Nov 20 '20 at 12:35
  • https://godbolt.org/z/a971GW works for me (timeouts). Here also it timeouts: https://wandbox.org/permlink/Y2YBuLYegnufDhXF – Marek R Nov 20 '20 at 12:38
  • @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. – David Schwartz Nov 20 '20 at 21:02

2 Answers2

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

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • 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. – Pete Becker Nov 20 '20 at 15:28
  • 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? – JHBonarius Nov 20 '20 at 16:17
  • 2
    @JHBonarius 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. – David Schwartz Nov 20 '20 at 21:01
  • 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? – JHBonarius Nov 22 '20 at 08:16
  • 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. – JHBonarius Nov 22 '20 at 08:23
  • 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. – David Schwartz Apr 23 '21 at 20:50
-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;
        }
    }
}
Evandro Coan
  • 8,560
  • 11
  • 83
  • 144