c++ should condition variable be notified under lock
Asked Answered
P

4

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";
}
Principal answered 3/3, 2016 at 14:55 Comment(13)
Would this answer your question?Abnormal
Possible duplicate of Why do pthreads’ condition variable functions require a mutex?Diathermy
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.Patricio
@Patricio it does ensure that it is in waiting state or haven't started yet, anyway it avoids race conditionDiluent
@Diathermy if that duplicate then example on cppreference is correct and question is valid, read example thereDiluent
@Slava: There is no race condition on condition variable because it is a synchronization primitive. So I do not need a protecting mutex.Patricio
@Patricio yes there is race condition, see this #31165248 and this #20982770Diluent
That was not the question, ready, data or processed are not part of the question.Patricio
Sidenote: Unless you are just goofing around, my recommendation would be not to write real applications in that style. Consider rather to have long-living threads/thread pools and a message based actor style of architecture. The processing actor then simply waits for a message in its mailbox and sends result back to the actor(s) who needs the result. Once you have a implementation of that scheme, you can use it for all sorts of applications. Better than doing it over and over again in ad hoc style, nah?Decanal
@Decanal Is there any such facility in the standard library? I'm refreshing my c++ after a long time, sticking to just c++11 standard facilities as I don't want to overwhelm myself.Principal
I think the library has some promises/futures thing in place. Never used it myself. For some actor framework, you might find some approaches with google. Boost has something like that, too, I think. Or you can do it yourself. In my past, I did the latter.Decanal
See for example: en.cppreference.com/w/cpp/thread/futureDecanal
@Decanal Thank you.Principal
A
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.

Antispasmodic answered 3/3, 2016 at 15:20 Comment(19)
In this particular code there may be no difference, but it is there in general, I already posted linksDiluent
@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)Antispasmodic
I posted link to one of my answer and one not mine, read second if you preferDiluent
Here is quote from pthread docs "if predictable scheduling behavior is required, then that mutex shall be locked by the thread calling pthread_cond_broadcast() or pthread_cond_signal()."Diluent
@Slava, this is pthread. However, std::condition_variable does not provide for predictable scheduling under any circumstances, so this point is 100% moot.Antispasmodic
you want me to create example program where I check and set flag under lock and it will miss a signal?Diluent
@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.Antispasmodic
I have a queue and pool of thread processing it. Missing signal means message in queue will not be processed in time. That is a moot point? HuhDiluent
@Slava, why did you remove your answer? In it's current form it does not meet standards for good SO answer, but you can easily re-arrange it, explaining that notifying under lock is important because of so and so and provide an illustration with (pseudo) code. Than people will have the option to voice their opinion and indicate their approval with upvotes or disagreement with downvotes.Antispasmodic
I removed it because I typed comment and by mistake put it into answer. I am working on example it just takes some timeDiluent
@Antispasmodic Is the following sequence possible if I move notify inside: 1) worker waiting on condition variable 2) main calls notify while holding lock 3) main has not yet released lock 4) worker wakes up and sees lock not yet released, though condition(ready) is true 5) worker goes back to wait 6) main releases lock. Here because main held lock while notify, worker couldn't proceed.Principal
@Nemo, you misunderstand how condition_variable works as well. Step 5) is not going to happen. Instead, worker will wait on mutex. When the mutex is released by main, worker will grab it and enter the loop (provided, no one else is waiting on the same mutex).Antispasmodic
@Antispasmodic Can you tell the sequence of events after step 3? Main called notify, but hasn't released mutex yet, worker receives notify and wakes up, tries to acquire mutex but cannot. Now how does worker keep checking for release of mutex?Principal
@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.Antispasmodic
@Antispasmodic Thanks Sergey. I put a sleep for two seconds after notify and see that the condition variable acquires the lock after two seconds and continues execution, which proves its able to detect the release of the mutex. How exactly is this mechanism implemented? After notify is made on the cv, the worker thread starts trying to acquire the lock continuously/periodically until it succeeds?Principal
@Nemo, are you asking how mutexes are implemented in general? Your question is not specific to condition variable.Antispasmodic
@Antispasmodic I think I have a better understanding now. Thanks for your answers!Principal
I downvoted this because you say that the notify_one() one is often only done inside the lock because you already have the lock anyway, but that there is no difference with doing it after the lock. That is incorrect.Fiftyfifty
@CarloWood if you think it is incorrect, what do you think is correct behavior?Antispasmodic
R
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

Reify answered 3/3, 2016 at 15:1 Comment(15)
Is there race condiation that will lead to missing signal if lock is not held when notify_one() is called?Diluent
overall mutexes and condition_variables cannot cause race condition within themselves, they are synchroniation primitives.Reify
@Diluent 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)Animate
@Animate 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 signalDiluent
@Slava, yes, sorry it was meant in general, the specific code here seems fine.Animate
@Animate even so I think this example is misleading, it should show general case where it is safe to send signal under lockDiluent
@Slava: You do not understand, and thanks for downvote.Patricio
@Patricio I am sorry but you do not understand, read links I gave you aboveDiluent
@DavidHaim that's not quite true read here #31165248 and here #20982770Diluent
@DavidHaim Didn't understand your point on relock. In the example, the lock goes out of scope and the call to notify_one() re-locks and sends signal as per your answer. If that was moved inside the scope, it wouldn't need to re-lock, therefore more efficient?Principal
@Principal your lock gets out of scope after you called notify_one. that fraction of the moment is the moment the condition variable wakes up and tries to re-lock the lockReify
@DavidHaim So, here's my understanding. The lock on the mutex goes out of scope after the closing brace "}". At that point, no one owns the mutex. Now the call to notify_one() just signals any waiting threads that the mutex is free. This essentially makes waiting easier instead of having to poll constantly. Is my understanding correct?Principal
many threads can sleep on the same cv. when you notify one - one of the sleeping therads wakes up and re-lock the lock the cv was put to sleep with. that is it. all the rest are unrelated.Reify
The PS is wrong, making the condition atomic will break the condition variable protocol. It must be modified under the same mutex as what cv.wait is waiting on.Misinform
Sorry, downvoted because it is incorrect. I agree with @Diluent that notifies can crucially be lost (as in: leading to starvation / dead lock kind of situtations). When you RELY on that one notify_one() to actually wake up one thread, then it must be done inside the lock. Indeed in this particular snippet that seems not to be the case.Fiftyfifty
A
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.

Arianna answered 17/12, 2022 at 3:34 Comment(0)
P
-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.

Patricio answered 3/3, 2016 at 15:4 Comment(3)
Unfortunately everybody can down vote correct answers.Patricio
This user is deeply confused.Antispasmodic
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.Fiftyfifty

© 2022 - 2025 — McMap. All rights reserved.