1

I started just recently learning about multithreading and in I looked up the concept of condition variable. I tried a small example to get a feeling of how it works. I read this stackoverflow post which helped me a bit but everything is not yet clear.

#include <iostream>
#include <mutex>
using namespace std;
bool g_ready = false;
mutex g_mutex;
condition_variable g_cv;


void senderThread()
{
    g_mutex.lock();
    g_ready = true;
    g_cv.notify_one();
    g_mutex.unlock();
}

void receiverThread()
{
    unique_lock<mutex> ul(g_mutex);
    g_cv.wait(ul, []() {return g_ready; });
    cout << "execute receiver task" << endl;
}

int main()
{
    thread t1(senderThread);
    thread t2(receiverThread);

    t1.join();
    t2.join();

    return 0;
}

Question : there are some things I didn't grasp. As I understand it, wait() needs as an argument the unique lock because if the condition is not fulfilled, it needs to release the mutex. However

  • Why is the unlock called inside the wait function?
  • Is there a reason why we cannot just pass the mutex as an argument to wait() and we need the unique_lock format?
  • What would the pseudocode of wait() look like?

added

this post addresses the second point/question. If I understand it correctly, it says that

void foo()
{
    unique_lock<mutex> lk(mut);
    // mut locked by this thread here
    while (not_ready)
        cv.wait(mut);
    // mut locked by this thread here
}

wouldn't allow to call lk.owns_lock() from within the wait() function, but why exacltly is it needed?

roi_saumon
  • 489
  • 4
  • 13
  • 1
    you need a reference wehre you can read documentation. I suggest this https://en.cppreference.com/w/cpp/thread/condition_variable/wait. it mentions that the second overload is equivalent to `while (!stop_waiting()) { wait(lock); }` (`stop_waiting` is the predicate). Please try to focus on one single question. Often the answer to one question answers also the others – 463035818_is_not_an_ai Nov 11 '22 at 08:42
  • 1
    btw the source code of the library implementation isnt written to replace documentation. It is often littered with implementation details and has special cases to support different versions of the standard. Its super hard to understand how to use something based on looking at the source, because by just looking at the code you cannot tell what is implementation details and what is important to you as caller – 463035818_is_not_an_ai Nov 11 '22 at 08:46
  • 1
    I've always found the name condition_variable misguided. It is not a variable but a signal to wakeup threads. So you always have the combination of a variable (of your own), a mutex and a signal (the condition_variable) to wakeup other threads so they can check the (new) value of your real variable. – Pepijn Kramer Nov 11 '22 at 09:06
  • 1
    Some more comments. Don't use global variables, if you have a mutex then put it in a class with the functions that need it (I usually have the mutex/condition_variable/variable in a helper class that I can inject into producer/consumer class). Do NOT manually lock and unlock the mutex but use std::scoped_lock (or std::unique_lock). And stop using `using namesapce std;` just type `std::` where needed (to avoid nameclashes in big projects) – Pepijn Kramer Nov 11 '22 at 09:08
  • @463035818_is_not_a_number, thank you for the reference for the pseudocode. What would the pseudocode of `wait(lock)`? – roi_saumon Nov 11 '22 at 09:10
  • 1
    I dont expect pseudocode of `wait(lock)` to be much enlightening, partly because it isnt something one can implement easily without compiler support. It just does that ["Atomically unlocks lock, blocks the current executing thread, and adds it to the list of threads waiting on *this. The thread will be unblocked when notify_all() or notify_one() is executed. It may also be unblocked spuriously. When unblocked, regardless of the reason, lock is reacquired and wait exits."](https://en.cppreference.com/w/cpp/thread/condition_variable/wait) – 463035818_is_not_an_ai Nov 11 '22 at 09:14
  • @PepijnKramer, thank you for the heads up. I am aware that that in general it is better to use "smart locks", smart pointers and so on and using namespace is just because the code is very short. However what I am still not sure is why there is not a possibility to pass g_mutex as a parameter to wait(). – roi_saumon Nov 11 '22 at 09:15
  • 1
    Pre and post condition of `wait` is that the mutex is locked. What would you gain by passing the mutex rather than the lock? I wondered myself why it has to be a `unique_lock` but I dont remember that I felt the urge to use a different or no lock with the condition variable – 463035818_is_not_an_ai Nov 11 '22 at 09:20
  • @463035818_is_not_a_number, regarding your previous comment, pardon my ignorance but what does "waiting on *this" mean? – roi_saumon Nov 11 '22 at 09:22
  • 1
    btw a little silly way to answer your question is: "You cannot pass the mutex directly because the converting constructor that converts a mutex to a unique lock is explicit. If it wasnt explicit you could pass the mutex directly" ;) – 463035818_is_not_an_ai Nov 11 '22 at 09:23
  • 1
    its from the documentation for `condition_variable::wait` so `*this` refers to the condition variable – 463035818_is_not_an_ai Nov 11 '22 at 09:23
  • I still think this quesiton needs more focus. You are asking 3 questions that are somewhat related but can be asked and answered seperately on their own. – 463035818_is_not_an_ai Nov 11 '22 at 09:25
  • @463035818_is_not_a_number, I was more expecting an overload of the wait() function but let me read on explicit keyword to understand your comment. Asking 3 separate questions seems frightening. – roi_saumon Nov 11 '22 at 09:27
  • 1
    The first two questions are basically answered here: [C++11: why does std::condition_variable use std::unique_lock?](https://stackoverflow.com/q/13099660/580083). – Daniel Langr Nov 11 '22 at 09:31
  • @DanielLangr, thank you for the link. Do you know why we need to call `lk.owns_lock()` inside the `wait()` function? – roi_saumon Nov 11 '22 at 10:47
  • 3
    You don't need to. The answer you're referring to never said it needed to. It says the implementation has the _option_ to do that if it thinks this will be useful. If the underlying mutex is not locked by the current thread, the behaviour is undefined, and the implementation can do whatever it wants (including some helpful diagnostics) – Useless Nov 11 '22 at 12:34

0 Answers0