1
// conditionVariable.cpp

#include <iostream>
#include <condition_variable>
#include <mutex>
#include <thread>

std::mutex mutex_;
std::condition_variable condVar;

bool dataReady{false};

void doTheWork(){
  std::cout << "Processing shared data." << std::endl;
}

void waitingForWork(){
    std::cout << "Worker: Waiting for work." << std::endl;
    std::unique_lock<std::mutex> lck(mutex_);
    condVar.wait(lck, []{ return dataReady; });
    doTheWork();
    std::cout << "Work done." << std::endl;
}

void setDataReady(){
    {
      std::lock_guard<std::mutex> lck(mutex_);
      dataReady = true;
    }
    std::cout << "Sender: Data is ready."  << std::endl;
    condVar.notify_one();
}

int main(){

  std::cout << std::endl;

  std::thread t1(waitingForWork);
  std::thread t2(setDataReady);

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

  std::cout << std::endl;
  
}

I am having trouble understanding how the std::conditional_variable::wait() in waitingForWork works. https://en.cppreference.com/w/cpp/thread/condition_variable/wait states that

wait causes the current thread to block until the condition variable is notified or a spurious wakeup occurs, optionally looping until some predicate is satisfied.

*1) 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. If this function exits via exception, lock is also reacquired. (until C++14)

What does "atomically unlocks lock" mean? It'll unlock mutex_, and thus allow t2 to acquire it and lock it with lock_guard?

If wait() unlocks the lock, how does this block the thread? Doesn't unlocking have the opposite effect of blocking?

  • 1
    Does this answer your question? [Why do pthreads’ condition variable functions require a mutex?](https://stackoverflow.com/questions/2763714/why-do-pthreads-condition-variable-functions-require-a-mutex) – pptaszni Aug 17 '20 at 07:06
  • 1
    @pptaszni No. It uses the verbiage "atomically unlocks mutex," which is what I am asking about here. – structuralengin Aug 17 '20 at 07:14
  • 1
    _If wait() unlocks the lock, how does this block the thread?_ If the thread requires the lock to grant exclusive access to the guarded data it may not proceed as long as the lock is unlocked. It has to be blocked until the lock can be locked again (e.g. in reaction to a notify.) – Scheff's Cat Aug 17 '20 at 07:20
  • "atomically unlocks mutex" - it means exactly that. `condition_variable::wait` literally calls `lock.unlock()` at the beginning. The reasons behind this requirement are all very well explained in the link that I gave you. "hod does this block the thread" - it is implementation defined. In [libpthread](http://git.savannah.gnu.org/cgit/hurd/libpthread.git/) you can see that `__pthread_timedblock` is used. – pptaszni Aug 17 '20 at 07:44
  • Read that again closely: `wait()` will never return or throw an exception until after it has _re-locked_ the lock. – Solomon Slow Aug 17 '20 at 15:19
  • @Scheff So, in this particular example with 2 threads, if `t2` acquires the mutex, then the conditional variable's wait function blocks `t1` until `t2` releases the mutex right? – structuralengin Aug 17 '20 at 16:16
  • `t1` calls `condVar.wait()`. `condVar.wait()` will unlock the mutex and call some OS function which will not return until wake-up. So, the `wait()` is blocking the (further) execution of `t1`. AFAIK, the unlocking of mutex in `t2` is not sufficient to wake-up `t1`. `t2` has to call `condVar.notify_one()` or `condVar.notify_all()` to force a wake-up. – Scheff's Cat Aug 18 '20 at 06:13
  • @Scheff It seems in this code, there is nothing preventing `t2` from locking the mutex first. This could possibly result in `t2` sending a notification before `t1` reaches `condVar.wait()`, resulting in the "lost wakeup" phenomenon? This seems like a big issue here. – structuralengin Aug 19 '20 at 05:22
  • I once had the same concern. My paranoid solution: `t1` locks mutex. `t1` checks availability of data. If data there then go on, else enter `condVar.wait()`. This works fine on my side. Btw. I didn't duplicate the code to check availability of data just continued after `condVar.wait()`. So, spurious wake-ups are handled as well (i.e. wake-up although data is not there). – Scheff's Cat Aug 19 '20 at 05:39
  • I just recalled [std::condition_variable::wait()](https://en.cppreference.com/w/cpp/thread/condition_variable/wait). Please, note _2) Equivalent to `while (!pred()) { wait(lock); }`_. So, if `t2` will produce data _before_ `t1` is started the first call of `condVar.wait()` will recognize this and just skip the part with actual waiting but return immediately (without unlocking and re-locking). – Scheff's Cat Aug 19 '20 at 05:43
  • @Scheff Ah that's a good point. It seems the second overload, on its face, is a bit more foolproof than the first. – structuralengin Aug 19 '20 at 06:19

0 Answers0