0
votes

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 ?

2
The examples you are referring to didn't use the overload of wait() with the predicate. Hence they basically reimplemented that one. Regardless, for your use case you should rather use a future, not an condition variable. - Ext3h
I need m_calculated_value to be ready with the required value when MyClass::GetCalculatedValue returns. this looks to me like a typical case of signalling between threads to tell that the data is ready. @Wxt3h Dont you think so as well? How will a future help here? Can you please elaborate a little more? - TheWaterProgrammer
I see many thi gs wrong, but not something that would cause your symptom. You ask more than one question and fail to provide a minimal reproducible example. There are SO Q&A that cover how to usr a condition variable, I advise reading those and do what they say. - Yakk - Adam Nevraumont
Does ComplexLogicToCalculateValue() take a long time? You could call that before taking the lock and assign it to a local variable. Only then take the lock and assign that local variable to the class member m_calculated_value. Not sure if this relates to your issue at all though. - Galik
You set the condition to false, then wait until it becomes true. If it was already true before that, it will not become true ever again. If it was false, setting it to false is useless. - n. 1.8e9-where's-my-share m.

2 Answers

3
votes

What is most likely to happen: Your worker thread owns and holds the mutex until it's done with the calculation. The main thread has to wait until it can acquire the lock. The worker will signal the CV before it releases the lock (in the destructor), by which time no other thread that would want to wait on the condition variable could have been acquired the lock that it still occupied by the notifying thread. Therefore the other thread never got a chance to wait on the condition variable at the time it gets notified as it just managed to acquire the lock after the notification event took place, causing it to wait infinitely.

The solution would be to remove the lock-acquisition in MyClass::ThreadFunctionToCalculateValue(), it is not required there at all, or at least, shouldn't be.

But anyways, why do you want to re-invent the wheel? For such problems, std::future has been created:

auto future = std::async(std::launch::async, ComplexLogicToCalculateValue);
bool is_ready = future.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
auto result = future.get();

Here, you can easily define timeouts, you don't have to worry about condition_variables and alike.

Cant I expect m_my_condition_variable to return out of wait when notify_one is called from other thread ?

No, not exclusively. Spurious wakeups still may occur.

0
votes

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();
}