Condition Variables w Predicate

Lost Wakeup

  • The example in the previous lecture has a problem

  • wait() will block until the condition variable is notified

  • If the writer calls notify() before the reader calls wait()

    • The condition variable is notified when there are no threads waiting
    • The reader will never be woken up
    • The reader could be blocked forever
  • This is known as a "lost wakeup"

Spurious Wakeup

  • Occasionally, the reader will be "spuriously" woken up

    • The reader thread has called wait()
    • The writing thread has not called notify()
    • The condition variable wakes the reader up anyway
  • This is due to the way that std::condition_variable is implemented

    • Avoiding spurious wakeups adds too much overhead
  • Fortunately, there is a way to solve both spurious and lost wakeups

wait() with Predicate

  • wait() takes an optional second argument: A predicate

  • Typically, the predicate checks a shared bool

    • The bool is initialized to false
    • It is set to true when the writer sends the notification
  • The reader thread will call this predicate

  • It will only call wait() if the predicate returns false

    • Also available for wait_for() and wait_until()

Using wait() with Predicate

  • Add a shared boolean flag, initialized to false
  • In the wait() call, provide a callable object that checks the flag

// bool flag for predicate
bool condition = false;

// Waiting thread
void reader() {
  // Lock the mutex
  std::unique_lock<std::mutex> uniq_lck(mut);

  // Lambda predicate that checks the flag
  cond_var.wait(uniq_lck, [] { return condition; });
  
  // ...
}

  • In the writer thread, set the flag to true
  {
    std::lock_guard<std::mutex> lck_guard(mut);
    sdata = "Populated";

    // Set the flag
    condition = true;
  }

  // notify the condition variable
  cv.notify_one();
}

Lost Wakeup Avoidance

  • The writer notifies the condition variable

  • The reader thread locks the mutex

  • The reader thread calls the predicate

  • If the predicate returns true

    • Lost wakeup scenario - the writer has already sent a notification
    • The reader thread continues, with the mutex locked
  • If the predicate returns false

    • Normal scenario
    • The reader thread calls wait() again

Multiple Threads

  • Condition variables are particularly useful here

    • Multiple threads are waiting for the same event
  • notify_all()

    • The condition variable wakes up all the threads which have called wait()
    • The threads could wake up in any order
    • All the reader threads process the data
  • notify_one()

    • Only one of the threads which called wait() will be woken up
    • The other waiting threads will remain blocked
    • A different reader thread processes the data each time