Search code examples
c++multithreadingc++11condition-variable

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


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

Solution

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