2

I am trying to understand the behaviour of condition_variable::wait_until. I have two pieces of code which I expect will wait one second and then exit. However, when I execute the programs (compiled with g++ 9.4.0, libc6 2.3.2) the first does not wait at all, whereas the second example does.

(see Update below for how to get correct behaviour in both examples)

Here's the first example which does not wait (unlike what I would expect) when executed:

// foo_passes_thru.cpp
// I compiled with `g++ foo_passes_thru.cpp`
#include <chrono>
#include <mutex>
#include <condition_variable>

std::condition_variable cv;
std::mutex m;

int main(int argc, char * argv[]) {

    using namespace std::chrono_literals;
    
    std::unique_lock<std::mutex> lk(m);
    const auto now = std::chrono::high_resolution_clock::now();
    cv.wait_until(lk, now + 1000ms);

    return 0;

}

And the second example, which, when executed, does wait as expected:

// foo_waits_1sec.cpp
// I compiled with `g++ foo_waits_1sec.cpp -lpthread`
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <thread>

std::condition_variable cv;
std::mutex m;

int main(int argc, char * argv[]) {

    using namespace std::chrono_literals;

    std::thread causes_wait([]{});
    // don't even need the thread to run
    causes_wait.join();

    std::unique_lock<std::mutex> lk(m);
    const auto now = std::chrono::high_resolution_clock::now();
    cv.wait_until(lk, now + 1000ms);

    return 0;

}
  • There does not appear to be a difference in the compiled wait_until.
  • In both programs, there is ultimately a call to pthread_cond_timedwait.

I am not sure if this is a case of unspecified behaviour or there is something incorrect in g++/STL/pthread. The documentation for pthread_cond_timedwait didn't suggest to me that there should be differences in behaviour.

Update

Upon posting, a related question came up that I hadn't yet seen. The behaviour that I expect is restored for the first example if I add the -pthread compiler option. This option should be used to build both.

user17732522
  • 53,019
  • 2
  • 56
  • 105
stephematician
  • 844
  • 6
  • 17
  • 1
    If anyone can explain why the `-pthread` command causes different behaviour to, say, `-lpthread` (which I had also tried with the first example) - that'd be nice. I wonder if - without `-pthread` - the call to `pthread_cond_timedwait` routes to some kind of stub? – stephematician Jan 02 '23 at 03:41

1 Answers1

2

wait_until is allowed to wake up spuriously. There is no guarantee that it will wait until the specified time. You need to call wait_until in a loop, check the return value to determine whether a timeout occurred and you need some condition to check in order to decide whether the non-timeout wakeup was spurious. Alternatively you can use the second overload of wait_until that takes the condition as a predicate and implements the loop.

Without the -pthread option the program is single-threaded and a corresponding implementation is probably being used. In that implementation there can never be a non-spurious wakeup, so I think it is not surprising that it would be implemented to return immediately, although I have not checked the sources (of glibc or whichever libc implementation you use).

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • The spurious wakeup reads more like a corner case to me as opposed to 'expected' behaviour. What I think is happening is that without `-pthread`, a stub for `pthread_cond_timedwait` is used (see https://stackoverflow.com/a/21104052/7050789 and https://codebrowser.dev/glibc/glibc/htl/forward.c.html) which returns immediately. – stephematician Jan 02 '23 at 12:41
  • I agree with your suggestion that you should check if a timeout occurred; as this is (as you say) not guaranteed. – stephematician Jan 02 '23 at 12:46
  • @stephematician My point is that simply returning immediately seems like a conforming implementation and one that makes sense for a single-threaded program in which there can't be any external change of the condition, so that it must be true initially. (Although I am not sure that there shouldn't be a clock check for timeout in the stub for actual conformance. In any case, it is not useful in a single-threaded program.) – user17732522 Jan 02 '23 at 23:00