Indeed the example does not show the issue but as a general principle when there is two instructions there is place for a race condition.
Here is an example that hopefully will illustrate the issue.
Say you manage the mutex yourself and you have two threads, thread#1 and thread#2:
- thread#1 needs to modify some shared state
- it acquires the mutex and changes the state
- it releases the mutex and waits for something to happen before proceeding
Here is some code :
pthread_mutex_lock(&mutex);
// change state
pthread_mutex_unlock(&mutex);
pthread_cond_wait(&cond);
There is more:
- thread#2 waits on the mutex, changes the shared state and signals thread#1 using the condition variable to give thread#1 a chance to do something before proceeding
Here is the code :
pthread_mutex_lock(&mutex);
// change state
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
Here is a nasty scenario :
- thread#1 acquires the lock and changes the shared state
- thread#1 releases the lock,
pthread_mutex_unlock(&mutex)
, and is preempted
- thread#2 acquires the lock,
pthread_mutex_lock(&mutex)
, changes state, unlocks, signals and is preempted
- thread#1 is scheduled again and waits on the condition,
pthread_cond_wait(&cond)
You have one issue that can escalate :
- the signal has been lost: by itself it can be more or less critical depending on your application
- but even if the signal is not important by itself thread#1 is now stuck waiting for a signal that already happened
- and even if thread#1 didn't do something important you can have a dead lock if later thread#2 waits for thread#1
So to avoid this issue thread#1 must wait immediately when it releases the locks before any other thread has any chance to signal the condition.
IMHO it's more the unlock/wait transition than the wake-up/lock transition that needs to be atomic.
I'm curious about scenarios in which the wake-up/lock transition would absolutely need to be atomic...
Hope this helps.