16

I found the following example for condition variable on www.cppreference.com, http://en.cppreference.com/w/cpp/thread/condition_variable. The call to cv.notify_one() is placed outside the lock. My question is if the call should be made while holding the lock to guarantee that waiting threads are in fact in waiting state and will receive the notify signal.

#include <iostream> #include <string> #include <thread> #include <mutex> #include <condition_variable> std::mutex m; std::condition_variable cv; std::string data; bool ready = false; bool processed = false; void worker_thread() { // Wait until main() sends data std::unique_lock<std::mutex> lk(m); cv.wait(lk, []{return ready;}); // after the wait, we own the lock. std::cout << "Worker thread is processing data\n"; data += " after processing"; // Send data back to main() processed = true; std::cout << "Worker thread signals data processing completed\n"; // Manual unlocking is done before notifying, to avoid waking up // the waiting thread only to block again (see notify_one for details) lk.unlock(); cv.notify_one(); } int main() { std::thread worker(worker_thread); data = "Example data"; // send data to the worker thread { std::lock_guard<std::mutex> lk(m); ready = true; std::cout << "main() signals data ready for processing\n"; } cv.notify_one(); // wait for the worker { std::unique_lock<std::mutex> lk(m); cv.wait(lk, []{return processed;}); } std::cout << "Back in main(), data = " << data << '\n'; worker.join(); } 

Should the notify_one() call be moved inside the lock to guarantee waiting threads receive the notify signal,

// send data to the worker thread { std::lock_guard<std::mutex> lk(m); ready = true; cv.notify_one(); std::cout << "main() signals data ready for processing\n"; } 
13
  • 3
    Would this answer your question? Commented Mar 3, 2016 at 14:58
  • 1
    Possible duplicate of Why do pthreads’ condition variable functions require a mutex? Commented Mar 3, 2016 at 14:58
  • The documentation answer your question explicitly, just read! For you: the lock does not need to be held for notification... And holding the mutex does not ensure that the thread is in waiting state. Commented Mar 3, 2016 at 15:01
  • @knivil it does ensure that it is in waiting state or haven't started yet, anyway it avoids race condition Commented Mar 3, 2016 at 15:05
  • @wilx if that duplicate then example on cppreference is correct and question is valid, read example there Commented Mar 3, 2016 at 15:07

5 Answers 5

10

You do not need to notify under lock. However, since notify is logically happening when the actual value is changed (otherwise, why would you notify?) and that change must happen under lock, it is often done within the lock.

There would be no practical observable difference.

Sign up to request clarification or add additional context in comments.

19 Comments

In this particular code there may be no difference, but it is there in general, I already posted links
@Slava, you posted links to your own answers. This is hardly an authority, sorry. I maintain my statements. There will be no race when used in proper manner (that is, locking before checking and locking before setting)
I posted link to one of my answer and one not mine, read second if you prefer
@Slava, this is the point you fail to understand. condition_variables are not signalling mechanisms. So 'missing a signal' is an absolutely moot point. conditiona_variable usage scenario is always the same: lock mutex, check data, unlock-and-wait.
@Nemo, it this point, conditional variable is out of the picture. The worker remains waiting on mutex the same way as if you simply tried locking an already locked mutex. You see, unlike entrance to wait, which is atomic (release mutex and start wait) exit from wait is not atomic. Thread first is woken up from wait (or wakes up spuriously!), than tries to grab mutex. If mutex is available, it's cool, if it is not, it is now a simple mutex wait. Once the mutex becomes avaible, thread continues. conditional variable might have signalled 1000 times in between, no one cares.
|
8

if I understand your question correctly, it's equivilant to "should the notifier thread lock the mutex while trying to notify some CV in other thread"

no, it is not mandatory and even does some counter-effect.
when condition_variable is notified from another thread it tries to-relock the mutex on which it was put to sleep. locking that mutex from its working thread will block the other thread which trying to lock it, untill the that lock-wrapper gets out of scope.

PS
if you do remove the locking from the function that send data to the worker threads, ready and processed should at least be atomics. currently they are synchronized by the lock , but when you remove the lock they cease to be thread-safe

15 Comments

Is there race condiation that will lead to missing signal if lock is not held when notify_one() is called?
overall mutexes and condition_variables cannot cause race condition within themselves, they are synchroniation primitives.
@Slava Yes, you can miss signals, but you can miss signals if it did hold the lock too - albeit your code seems safe from that due to the cv.wait(lk, []{return ready;}) , regardless of the notify_one() being done with the lock held or not. (But setting the ready flag must be done with the lock held)
@nos no there is no race condition if notify is sent under lock, waiting thread either will check the flag or wakeup. Without lock it may check flag and then miss the signal
|
0

There is a scenario where it is crucial that the lock is held while notify_all is called: when the condition_variable is destroyed after waiting, a spurious wake-up can cause the wait to be ended and the condition_variable destroyed before notify_all is called on the already destroyed object.

Comments

0

If I notify the condvar from outside notifying 31 threads on my machine I get three times the CPU-time.

#include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <atomic> #include <semaphore> #include <vector> #include <sys/resource.h> using namespace std; int main( int argc, char **argv ) { constexpr size_t N = 10'000; int nClients = thread::hardware_concurrency() - 1; mutex mtx; int signalled = 0; condition_variable cv; atomic_int ai( 0 ); binary_semaphore bs( false ); vector<jthread> clients; atomic_int64_t nVoluntary( 0 ); for( int c = nClients; c; --c ) clients.emplace_back( [&] { for( size_t r = N; r; --r ) { unique_lock lock( mtx ); cv.wait( lock, [&] { return (bool)signalled; } ); --signalled; lock.unlock(); if( ai.fetch_sub( 1, memory_order_relaxed ) == 1 ) bs.release( 1 ); } rusage ru; getrusage( RUSAGE_THREAD, &ru ); nVoluntary.fetch_add( ru.ru_nvcsw, memory_order_relaxed ); } ); constexpr bool Inside = true; for( size_t r = N; r; --r ) { auto notify = [&] { if( argc < 2 ) cv.notify_all(); else for( int c = nClients; c; cv.notify_one(), --c ); }; unique_lock lock( mtx ); signalled = nClients; if( Inside ) notify(); ai.store( nClients, memory_order_relaxed ); lock.unlock(); if( !Inside ) notify(); bs.acquire(); } clients.resize( 0 ); cout << N << " rounds," << endl; cout << (double)nVoluntary.load( memory_order_relaxed ) / nClients << " context switches per thread" << endl; } 

Comments

-5

If you do not wait on a condition variable then the notification is lost. It does not matter if you are holding any locks. A condition variable is a synchronization primitive and do not need a lock for protection.

You can miss signals with and without lock. The mutex protects only normal data like ready or processed.

3 Comments

Unfortunately everybody can down vote correct answers.
This user is deeply confused.
This is also incorrect because the essence of a wait() is that it atomically unlocks the mutex and begins to wait, which guarantees that any notify done after the lock is released will be seen by the waiting thread. So you can't say "A condition variable ... does not need a lock for protection" when it is such an essential part of it. Not downvoted though.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.