When I wake up a thread waiting on a condition variable while holding the corresponding mutex, can I assume that the woken thread will run after I release the mutex and before anyone else (myself included) can lock the mutex again? Or can I only be sure that it will run at some point in the future?
To be precise, assume that I have the following functions.
bool taken = false;
int waiting = 0;
pthread_mutex_t m; // properly initialised elsewhere
pthread_cond_t c;
void enter() {
pthread_mutex_lock(&m);
// Is `taken || (waiting == 0)` guaranteed here?
while (taken) {
++ waiting;
pthread_cond_wait(&c, &m);
-- waiting;
}
taken = true;
pthread_mutex_unlock(&m);
}
void leave() {
pthread_mutex_lock(&m);
taken = false;
if (waiting > 0) {
pthread_cond_signal(&c);
}
pthread_mutex_unlock(&m);
}
(This is a toy example, not meant to be useful.)
Also assume that all threads are using these functions as
enter()
// some work here
leave()
Can I then be sure that directly after acquiring the mutex in enter()
(see comment in the code), if taken
is false waiting
has to be zero? [It seems to me that this should be the case, because the woken thread will assume to find the state that the waking thread has left behind (if the wake-up was not spurious) and this could otherwise not be guaranteed, but I did not find it clearly worded anywhere.]
I am mainly interested in the behaviour on (modern) Linux, but of course knowing whether this is defined by POSIX would also be of interest.
Note: This may have already been asked in another question, but I hope that mine is clearer.
After t0
does pthread_cond_signal
, t1
is no longer waiting for the condition variable, but it is also not running, because t0
still holds the mutex; t1
is instead waiting for the mutex. The thread t2
may also be waiting for the mutex, at the beginning of enter()
. Now t0
releases the mutex. Both t1
and t2
are waiting for it. Is t1
handled in a special way and guaranteed to get it, or could t2
get it instead?
Thanks for your comment, Carsten, that cleared it up.
No, see this answer for more reference. Given your example, t2
could acquire the mutex before t1
and a race condition may occur leading to unexpected outcomes.
Reiterating, t0
may initially have the mutex and be in the while loop holding on the line pthread_cond_wait(&c, &m);
and the mutex is released atomically reference. t1
could call leave()
acquiring the mutex signaling the condition c
, and then release the mutex. t0
will prepare to run --waiting
by trying to acquire the now-freed mutex by t1
, but it can become context switched by the OS. Some other thread t2
waiting on the mutex can grab it and run enter()
causing undesired outcomes, see non-reentrant. t2
then releases the mutex. t0
may swaps back only to see values have been mutated.