How to use a condition_variable to really wait_for no longer than a certain duration
Asked Answered
T

3

22

As it turns out, condition_variable::wait_for should really be called condition_variable::wait_for_or_possibly_indefinitely_longer_than, because it needs to reacquire the lock before really timing out and returning.

See this program for a demonstration.

Is there a way to express, "Look, I really only have two seconds. If myPredicate() is still false at that time and/or the lock is still locked, I don't care, just carry on regardless and give me a way to detect that."

Something like:

bool myPredicate();
auto sec = std::chrono::seconds(1);

bool pred;
std::condition_variable::cv_status timedOut;

std::tie( pred, timedOut ) =
    cv.really_wait_for_no_longer_than( lck, 2*sec, myPredicate );

if( lck.owns_lock() ) {
    // Can use mutexed resource.
    // ...
    lck.unlock();
} else {
    // Cannot use mutexed resource. Deal with it.
};
Trepang answered 8/8, 2014 at 1:7 Comment(3)
I'm afraid wait_until suffers from the same "feature". "Once notified or once it is abs_time, the function unblocks and calls lck.lock(), leaving lck in the same state as when the function was called. Then the function returns (notice that this last mutex locking may block again the thread before returning)."Trepang
If you don't want to be acquiring a mutex on awakening, condition variables won't work for you, in any language.Truffle
Fair enough, thanks. How would you do it? timed_mutex/locks?Trepang
I
12

I think that you misuse the condition_variable's lock. It's for protecting condition only, not for protecting a time-consuming work.

Your example can be fixed easily by splitting the mutex into two - one is for critical section, another is for protecting modifications of ready condition. Here is the modified fragment:

typedef std::unique_lock<std::mutex> lock_type;
auto sec = std::chrono::seconds(1);
std::mutex mtx_work;
std::mutex mtx_ready;
std::condition_variable cv;
bool ready = false;

void task1() {
    log("Starting task 1. Waiting on cv for 2 secs.");
    lock_type lck(mtx_ready);
    bool done = cv.wait_for(lck, 2*sec, []{log("Checking condition..."); return ready;});
    std::stringstream ss;
    ss << "Task 1 finished, done==" << (done?"true":"false") << ", " << (lck.owns_lock()?"lock owned":"lock not owned");
    log(ss.str());
}

void task2() {
    // Allow task1 to go first
    std::this_thread::sleep_for(1*sec);
    log("Starting task 2. Locking and sleeping 2 secs.");
    lock_type lck1(mtx_work);
    std::this_thread::sleep_for(2*sec);
    lock_type lck2(mtx_ready);
    ready = true; // This happens around 3s into the program
    log("OK, task 2 unlocking...");
    lck2.unlock();
    cv.notify_one();
}

It's output:

@2 ms: Starting task 1. Waiting on cv for 2 secs.
@2 ms: Checking condition...
@1002 ms: Starting task 2. Locking and sleeping 2 secs.
@2002 ms: Checking condition...
@2002 ms: Task 1 finished, done==false, lock owned
@3002 ms: OK, task 2 unlocking...
Ingenuous answered 8/8, 2014 at 15:30 Comment(2)
Many thanks, you make a valid point. Though strictly speaking this isn't really answering the question, it's suggesting how to avoid that situation, which in theory may not be avoidable. I'll accept as a solution if nothing else comes up. Thanks.Trepang
Another point to make is that code that acquires a mutex should generally not hold it for long periods of time. Particularly if other areas that acquire the mutex can't afford to block for that long period of time.Drupelet
G
1

Actually, the condition_variable::wait_for does exactly what you want. The problem with your example is that you locked a 2-second sleep along with the ready = true assignment, making it impossible for the condition variable to even evaluate the predicate before reaching the time limit.

Put that std::this_thread::sleep_for(2*sec); line outside the lock and see for yourself.

Giannagianni answered 14/9, 2016 at 1:18 Comment(0)
I
0

Is there a way to express, "Look, I really only have two seconds. If myPredicate() is still false at that time and/or the lock is still locked, I don't care, just carry on regardless ..."

Yes, there is a way, but unfortunately in case of wait_for it has to be manual. The wait_for waits indefinitely because of Spurious Wakeup. Imagine your loop like this:

while(!myPredicate())
  cv.wait_for(lock, std::chrono::duration::seconds(2);

The spurious wakeup can happen anytime in an arbitrary platform. Imagine that in your case it happens within 200 ms. Due to this, without any external notification wait_for() will wakeup and check for myPredicate() in the loop condition.

As expected, the condition will be false, hence the loop will be true and again it will execute cv.wait_for(..), with fresh 2 seconds. This is how it will run infinitely.

Either you control that updation duration by yourself or use wait_until() which is ultimately called in wait_for().

Inly answered 7/8, 2015 at 7:37 Comment(1)
Note it's actually std::chrono::seconds (no duration in namespace)` - en.cppreference.com/w/cpp/chrono/duration.Elma

© 2022 - 2024 — McMap. All rights reserved.