1

My thread does not need to be locked. std::unique_lock locks thread on construction. I am simply using cond_var.wait() as a way to avoid busy waiting. I have essentially circumvented the auto-locking by putting the unique_lock within a tiny scope and hence destroying the unique lock after it leaves the tiny scope. Additionally, there is only a single consumer thread if that's relevant.

{
std::unique_lock<std::mutex> dispatch_ul(dispatch_mtx);
pq_cond.wait(dispatch_ul);
}

Is there possibly a better option to avoid the unnecessary auto-lock functionality from the unique_lock? I'm looking for a mutexless option to simply signal the thread, I am aware of std::condition_variable_any but that requires a mutex of sorts which is yet again unnessesary in my case.

  • We need to see the way the code decides whether or not to wait to answer your question. What is the thread waiting for? How does it know is hasn't already happened? You haven't shown us nearly enough code. – David Schwartz Mar 03 '22 at 23:26

1 Answers1

3

You need a lock to prevent this common newbie mistake:

  1. Producer thread produces something,
  2. Producer thread calls some_condition.notify_all(),
  3. Producer thread goes idle for a while,

meanwhile:

  1. Consumer thread calls some_condition.wait(...)
  2. Consumer thread waits,...
  3. And waits,...
  4. And waits.

A condition variable is not a flag. It does not remember that it was notified. If the producer calls notify_one() or notify_all() before the consumer has entered the wait() call, then the notification is "lost."

In order to prevent lost notifications, there must be some shared data that tells the consumer whether or not it needs to wait, and there must be a lock to protect the shared data.

The producer should:

  1. Lock the lock,
  2. update the shared data,
  3. notify the condition variable,
  4. release the lock

The consumer must then:

  1. Lock the lock,
  2. Check the shared data to see if it needs wait,
  3. Wait if needed,
  4. consume whatever,
  5. release the lock.

The consumer needs to pass the lock in to the wait(...) call so that wait(...) can temporarily unlock it, and then re-lock it before returning. If wait(...) did not unlock the lock, then the producer would never be able to reach the notify() call.

Solomon Slow
  • 25,130
  • 5
  • 37
  • 57
  • ohhhh @Solomon Slow , so the `.wait()` unlocks the lock anyways? only the `.wait(unique_lock)` requires a unique lock the. the `notify_one()` does not have a parameter. so how would the unique lock have an effect on the producer? in future, I think I would use a `std::counting_semaphore` for this application, instead do you agree? You seem like such a pro, I hope to be as knowledgeable as you in multithreading one day. – Chris Kouts Mar 03 '22 at 23:27
  • @ChrisKouts, The first thing `cv.wait()` does is, it adds the calling thread to the wait set for the condition variable, `cv`, and it unlocks the given lock as a single, atomic operation. "Atomic" in this case, means that there's no possible way for the producer to acquire the lock and call `notify_xxxx()` before the consumer thread is established as waiting-to-be-notified. Later, after `cv` has been notified, the `wait()` call always acquires the lock again before it returns. – Solomon Slow Mar 03 '22 at 23:33
  • Ahhh @Solomon Slow , so the `unique_lock` is simply needed as a parameter for the `wait()` as it provides the necessary code to halt a thread(or possibly many therads). I feel dumb lol. thanks! – Chris Kouts Mar 03 '22 at 23:35
  • @ChrisKouts, `std::counting_semaphore` and `std::binary_semaphore` solve different problems from `std::condition_variable`. If one of them solves _your_ problem, then go for it! – Solomon Slow Mar 03 '22 at 23:35
  • Re, "I feel dumb." We've all been there. – Solomon Slow Mar 03 '22 at 23:37
  • it just clicked thanks so much! – Chris Kouts Mar 03 '22 at 23:39