Notification texts go here Contact Us Buy Now!

C++ condition_variable why is lock required?

A std::lock_guard is required to ensure proper synchronization between threads when using a std::condition_variable.

Without the lock in question, ready = true; is not synchronized with worker_thread reading ready. That's a data race and has undefined behavior.

    {
        // std::lock_guard lk(m); // removed
        ready = true;             // race condition
    }

With the removed sychronization primitive, changes to ready in one thread doesn't require the compiler to produce any code that makes sense to you as a programmer.

The waiting thread may never see the change in ready and the compiler may let the waiting thread observe a "cached" value of false - since, again, you didn't tell the compiler that there's some synchronization needed. If the two threads do not synchronize, why then should the compiler assume that the value of the variable will ever change in the thread that reads it? If you don't tell it that there's a writer it may simply replace the reading of variable with a hard value, be it true or false.

The compiler could also lay out the final assembly as if ready was set all along. It's allowed to go any way it wants when you don't synchronize events.

You also don't know when the waiting thread wakes up. It may not only be woken up by notify_one since there's also spurious wakeups, which is why the condition must be checked after waking up. If it's not true, go back to waiting.

Another answer is that you can't use a condition variable without a mutex. They're simply not designed to work that way.

You could, for one possibility, make ready an atomic<bool>, which would guarantee that the change in its value would propagate to the other thread correctly. You'd still need the mutex for the cv though, so you might as well use the mutex to protect ready as well (in which case, you don't need to make ready atomic, because the mutex guarantees propagation, just like making it atomic does).

Here is an alternative using atomic_flag:

#include <atomic>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>

std::atomic_flag ready {};
std::atomic_flag processed {};

std::string data;
 
void worker_thread()
{
    // Wait until main() sends data
    ready.wait(false);
    ready.clear();

    std::cout << "Worker thread is processing data\n";
    data += " after processing";

    processed.test_and_set();
    processed.notify_one();
    std::cout << "Worker thread signals data processing completed\n";
}
 
int main()
{
    std::thread worker(worker_thread);
 
    data = "Example data";
    ready.test_and_set();
    ready.notify_one();
    std::cout << "main() signals data ready for processing\n";
 
    processed.wait(false);
    std::cout << "Back in main(), data = " << data << '\n';
 
    worker.join();
}

This can often reduce overhead compared to using a condition variable.

Finally, a std::lock_guard is required to ensure that the std::condition_variable is not notified while the lock is held, which could lead to a race condition.

Post a Comment

Cookie Consent
We serve cookies on this site to analyze traffic, remember your preferences, and optimize your experience.
Oops!
It seems there is something wrong with your internet connection. Please connect to the internet and start browsing again.
AdBlock Detected!
We have detected that you are using adblocking plugin in your browser.
The revenue we earn by the advertisements is used to manage this website, we request you to whitelist our website in your adblocking plugin.
Site is Blocked
Sorry! This site is not available in your country.