Search code examples
multithreadingc++11mutexcondition-variabletimed

Timed waiting and infinite waiting on the same condition variable?


Scenario:
I have a condition_variable based wait and signal mechanism. This works! But I need a little more than just the classic wait and signal mechanism. I need to be able to do a timed wait as well as an infinite wait "on the same condition_variable". Hence, I created a wrapper class around a condition_variable which takes care of the spurious wake up issue as well. Following is the code for that:

Code:

// CondVarWrapper.hpp
#pragma once
#include <mutex>
#include <chrono>
#include <condition_variable>

class CondVarWrapper {
public:
    void Signal() {
        std::unique_lock<std::mutex> unique_lock(cv_mutex);
        cond_var_signalled = true;
        timed_out = false;
        unique_lock.unlock();
        cond_var.notify_one();
    }

    bool WaitFor(const std::chrono::seconds timeout) {
        std::unique_lock<std::mutex> unique_lock(cv_mutex);
        timed_out = true;
        cond_var.wait_for(unique_lock, timeout, [this] {
            return cond_var_signalled;
        });
        cond_var_signalled = false;
        return (timed_out == false);
    }

    bool Wait() {
        std::unique_lock<std::mutex> unique_lock(cv_mutex);
        timed_out = true;
        cond_var.wait(unique_lock, [this] {
            return cond_var_signalled;
        });
        cond_var_signalled = false;
        return (timed_out == false);
    }

private:
    bool cond_var_signalled = false;
    bool timed_out  = false;
    std::mutex  cv_mutex;
    std::condition_variable cond_var;
};


// main.cpp
#include "CondVarWrapper.hpp"
#include <iostream>
#include <string>
#include <thread>

int main() {
   CondVarWrapper cond_var_wrapper;

   std::thread my_thread = std::thread([&cond_var_wrapper]{
       std::cout << "Thread started" << std::endl;
        if (cond_var_wrapper.WaitFor(std::chrono::seconds(10))) {
            std::cout << "Thread stopped by signal from main" << std::endl;
        } else {
            std::cout << "ERROR: Thread stopping because of timeout" << std::endl;
        }
    });

   std::this_thread::sleep_for(std::chrono::seconds(3));
   // Uncomment following line to see the timeout working
   cond_var_wrapper.Signal();
   my_thread.join();
}

Question:
Above code is good but I think there is one problem? Would I really be able to do a wait as as well do a wait_for on the same condition_variable? What if a thread has acquired cv_mutex by calling CondVarWrapper::Wait() and this one never returned for some reason. And then another thread comes in calling CondVarWrapper::WaitFor(std::chrono::seconds(3)) expecting to return out if it does not succeed in 3 seconds. Now, this second thread would not be able to return out of WaitFor after 3 seconds isnt it? In fact it wouldn't ever return. Because the condition_variable wait is a timed wait but not the lock on cv_mutex. Am I correct or Am I wrong in understanding here?

If I am correct above then I need to replace std::mutex cv_mutex with a std::timed_mutex cv_mutex and do a timed_wait in CondVarWrapper::WaitFor and do a infinite wait on CondVarWrapper::Wait? Or are there any better/easier ways of handling it?


Solution

  • The mutex is released when calling std::condition::wait on the condition variable cond_var. Thus, when you call CondVarWrapper::Wait from one thread, it releases the mutex when calling std::condition::wait and it hangs in there forever, the second thread can still call CondVarWrapper::WaitFor and successfully lock the mutex cv_mutex.