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

C++ : How to use an std::condition_variable between UI thread & worker std::thread


I am trying to use an std::condition_variable from C++11 for a data transaction between between UI thread & worker thread.

Situation:
m_calculated_value is a value which calculated after a complex logic. This is required on a trigger of a event from the UI thread. UI thread calls MyClass::GetCalculatedValue to fetch the value of m_calculated_value which needs to be calculated by the worker thread function that is MyClass::ThreadFunctionToCalculateValue.

Code:

std::mutex              m_mutex;
std::condition_variable m_my_condition_variable;
bool                    m_value_ready;
unsigned int            m_calculated_value;


// Gets called from UI thread
unsigned int MyClass::GetCalculatedValue() {

    std::unique_lock<std::mutex> lock(m_mutex);
    m_value_ready = false;

    m_my_condition_variable.wait(lock, std::bind(&MyClass::IsValueReady, this));

    return m_calculated_value;
}


bool MyClass::IsValueReady() {

    return m_value_ready;
}

// Gets called from an std::thread or worker thread
void MyClass::ThreadFunctionToCalculateValue() {

    std::unique_lock<std::mutex> lock(m_mutex);

    m_calculated_value = ComplexLogicToCalculateValue();
    m_value_ready = true;

    m_my_condition_variable.notify_one();
}

Problem:
But the problem is that m_my_condition_variable.wait never returns.

Question:
What am I doing wrong here?

Is it a correct approach to make UI thread wait on a condition variable signal from worker thread? How do I get out of a situation where the condition_variable never triggers due to an error in the worker thread function? Is there a way I can somehow use a timeout here?

Trying to understand how it works:
I see in many examples they use a while loop checking the state of a boolean variable around a condition_var.wait. Whats the point of loop around on a variable? Cant I expect m_my_condition_variable to return out of wait when notify_one is called from other thread ?


Solution

  • Take a look at this example here:

    http://en.cppreference.com/w/cpp/thread/condition_variable

    Changes to the code in question noted in comments in the example code below. You might want to consider using the same "handshake" as used in the cppreference.com example to synchronize when it's safe to calculate a new value (the UI thread has a wait / notify, the worker thread has a notify / wait).

    Before condition variable wait, the lock needs to be locked. The wait will unlock, wait for a notify, then lock and with the predicate function, check for ready and if not ready (spurious wake up), repeat the cycle.

    Before notify_one, the lock should be unlocked, else the wait gets woke up, but fails to get a lock (since it's still locked).

    std::mutex              m_mutex;
    std::condition_variable m_my_condition_variable;
    bool                    m_value_ready = false;  // init to false
    unsigned int            m_calculated_value;
    
    
    // Gets called from UI thread
    unsigned int MyClass::GetCalculatedValue() {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_my_condition_variable.wait(lock, std::bind(&MyClass::IsValueReady, this));
        m_value_ready = false;    // don't change until after wait
        return m_calculated_value;
    }  // auto unlock after leaving function scope
    
    bool MyClass::IsValueReady() {
    
        return m_value_ready;
    }
    
    // Gets called from an std::thread or worker thread
    void MyClass::ThreadFunctionToCalculateValue() {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_calculated_value = ComplexLogicToCalculateValue();
        m_value_ready = true;
        lock.unlock();         // unlock before notify
        m_my_condition_variable.notify_one();
    }
    

    or alternative:

    // Gets called from an std::thread or worker thread
    void MyClass::ThreadFunctionToCalculateValue() {
    
        {   // auto unlock after leaving block scope
            std::lock_guard<std::mutex> lock(m_mutex);
            m_calculated_value = ComplexLogicToCalculateValue();
            m_value_ready = true;
        }   // unlock occurs here
        m_my_condition_variable.notify_one();
    }