std::condition_variable::notify_one() called several times without context switching
Asked Answered
S

1

4

How many waiting threads will wake up in this example:

1st thread:

void wakeUp2Threads()
{
    std::unique_lock<std::mutex> lock(condvar_mutex);

    condvar.notify_one();
    condvar.notify_one();
}

2nd thread:

{
    std::unique_lock<std::mutex> lock(condvar_mutex);

    condvar.wait(lock); <- 2nd thread has entered here before 1st thread entered wakeUp2Threads.
}

3rd thread (the same as 2nd):

{
    std::unique_lock<std::mutex> lock(condvar_mutex);

    condvar.wait(lock); <- 3rd thread has entered here before 1st thread entered wakeUp2Threads.
}

Is there any guarantee that in this example both notifications will be delivered to different threads, not the same thread several times?

I.e what does notify_one() mean:

1) notify one thread, no matter has it been already notified (but has not been woken up yet), or not. (* see note)
or
2) notify one thread, but only this one, which has not been notified yet.

(*) Pay attention! I'm not talking here about scenario "waiting thread had already been notified somewhere in the past, woken up, done some stuff and entered condvar.wait() again" - of course, in this case several notify_one() routines can wake up the same thread over and over again.

I'm talking about another case:

notify_one() has notified waiting thread about wake-up, but BEFORE this waiting thread has received time slot form the kernel scheduler and continued execution - another notify_one() has been called again. Is it possible this second notification will be delivered to the same thread again, while it hasn't been woken up from first notification yet?

Sculley answered 26/2, 2013 at 9:45 Comment(2)
In the second and third thread, I assume you actually call the wait function with the lock and not the mutex?Poirier
yes, it's a mistake, I'll fix it right nowSculley
P
8

The notify_one call atomically unblocks one thread. That means that when called a second time it can't unblock the same thread as it's no longer blocked.

This is specified in the standard at section 30.5/3 and 30.5.1/7.

Poirier answered 26/2, 2013 at 9:51 Comment(11)
I've read the standard about this issue. Yes, waiting process waking up before mutex reacquiring. But how one process can ensure another process has woken up? Am I understand correctly that notifying thread just remove waiting process from some king of "waiting for unblock" queue inside kernel, and from that point scheduler just treat waiting thread as an active one?Sculley
@Sculley Yes that's correct. All modern kernels have different queues for blocked and unblocked processes/threads, or the very least a flag saying if it's in a blocked state or not.Poirier
Well. This explains how it should be. Too bad the standard is unclear about this. It doesn't say condvar is unblocked inside notify_one() routine.Sculley
Or maybe it says it in 30.5.1/7. Anyway. I don't think some library implements another behavior.Sculley
@Sculley Basically your thread transitioned from waiting to trying to acquire the mutex (locking), thus won't be woken up when a second is called.Semmes
@RedXIII Where do you read about that? Is it a part of the standard, or you are talking about possible implementation of condition variable?Sculley
@Sculley "The execution of wait, wait_for, and wait_until shall be performed in three atomic parts: 1. the release of the mutex and entry into the waiting state; 2. the unblocking of the wait; and 3. the reacquisition of the lock. — When unblocked, calls lock.lock() (possibly blocking on the lock), then returns. — The function will unblock when signaled by a call to notify_one() ...".Semmes
@Sculley After being unblocked in wait() you stop being in the wait state, as you atomically left it. Then you land in blocking state when you try to take the lock. You don't care about the conditional variable any more. If the condvar could call the same thread over and over then the sense of notify_one() is none. BTW, how should the multiply-woken-up thread behave? Ought it stop waiting on lock (even possible?) and relock it once more? What for? IMHO there it's hard for me to understand the logic behind such implementation, left alone implementation.Semmes
Sorry, I've understood your previous comment incorrectly. Yes, I've read this part of standard, and don't have any question about condvar now.Sculley
@RedXIII Yes, I understand this now. "BTW, how should the multiply-woken-up thread behave?" - I thought it can just ignore second notification.Sculley
Also note that it is not necessary to take the lock in wakeUp2Threads(), in which case one thread might immediately start executing real code before you call the second notify_one instead of hanging immediately on the mutex again. Performance wise that would be better.Maturation

© 2022 - 2024 — McMap. All rights reserved.