21

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.
};
Julian
  • 4,170
  • 4
  • 20
  • 27
  • I'm afraid [`wait_until`](http://www.cplusplus.com/reference/condition_variable/condition_variable/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)." – Julian Aug 08 '14 at 02:06
  • 9
    If you don't want to be acquiring a mutex on awakening, condition variables won't work for you, in any language. – Cubbi Aug 08 '14 at 02:26
  • Fair enough, thanks. How would you do it? timed_mutex/locks? – Julian Aug 08 '14 at 02:49

3 Answers3

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...
Anton
  • 6,349
  • 1
  • 25
  • 53
  • 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. – Julian Aug 09 '14 at 00:59
  • 1
    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. – Michael Burr Aug 07 '15 at 08:07
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.

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

Dhia Hassen
  • 508
  • 4
  • 20
iammilind
  • 68,093
  • 33
  • 169
  • 336
  • Note it's actually `std::chrono::seconds` (no duration in namespace)` - https://en.cppreference.com/w/cpp/chrono/duration. – studgeek Dec 10 '21 at 20:40