mutex and condition variable synchronization in different threads

The name of the picture


mutex and condition variable synchronization in different threads



Since I have recently started coding multi threaded programs this might be a stupid question. I found out about the awesome mutex and condition variable usage. From as far as I can understand there use is:



Now Consider the following class example:


class Queue {
private:
std::queue<std::string> m_queue;
boost::mutex m_mutex;
boost::condition_variable m_cond;
bool m_exit;

public:
Queue()
: m_queue()
, m_mutex()
, m_cond()
, m_exit(false)
{}

void Enqueue(const std::string& Req)
{
boost::mutex::scoped_lock lock(m_mutex);
m_queue.push(Req);
m_cond.notify_all();
}

std::string Dequeue()
{
boost::mutex::scoped_lock lock(m_mutex);
while(m_queue.empty() && !m_exit)
{
m_cond.wait(lock);
}

if (m_queue.empty() && m_exit) return "";

std::string val = m_queue.front();
m_queue.pop();
return val;
}

void Exit()
{
boost::mutex::scoped_lock lock(m_mutex);
m_exit = true;
m_cond.notify_all();
}
}



In the above example, Exit() can be called and it will notify the threads waiting on Dequeue that it's time to exit without waiting for more data in the queue.
My question is since Dequeue has acquired the lock(m_mutex), how can Exit acquire the same lock(m_mutex)? Isn't unless the Dequeue releases the lock then only Exit can acquire it?



I have seen this pattern in Destructor implementation too, using same class member mutex, Destructor notifies all the threads(class methods) thats it time to terminate their respective loops/functions etc.





There is m_cond.wait(lock); which releases the lock.
– Jarod42
6 hours ago


m_cond.wait(lock);





You pass the lock to your wait call. It will unlock the mutex before waiting.
– super
6 hours ago







The documentation is pretty clear en.cppreference.com/w/cpp/thread/condition_variable
– Maxim Egorushkin
5 hours ago




1 Answer
1



As Jarod mentions in the comments, the call


m_cond.wait(lock)



is guaranteed to atomically unlock the mutex, releasing it for the thread, and starts listening to notifications of the condition variable (see e.g. here).
This atomicity also ensures any code in the thread is executed after the listening is set up (so no notify calls will be missed). This assumes of course that the thread first locks the mutex, otherwise all bets are off.



Another important bit to understand is that condition variables may suffer from "spurious wakeups", so it is important to have a second boolean condition (e.g. here, you could check the emptiness of your queue) so that you don't end up awoken with an empty queue. Something like this:


m_cond.wait(lock, [this]() { return !m_queue.empty() || m_exit; });





Thank you Jarod, super and rubenvb. Its very subtle. Good to know!
– zzz...
5 hours ago





I added a small important bit (the thread needs to lock the mutex), otherwise there is not enough synchronization of course. It is all very subtle, and easy to get wrong (but also IMO easy to get right once you understand what is going on). I do suggest wrapping this (as you did) in a fixed interface so you don't need to worry about these details after your "queue" component is finished.
– rubenvb
5 hours ago






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Stripe::AuthenticationError No API key provided. Set your API key using “Stripe.api_key = ”

CRM reporting Extension - SSRS instance is blank

Keycloak server returning user_not_found error when user is already imported with LDAP