Do I have to acquire lock before calling condition_variable.notify_one()?
You do not need to be holding a lock when calling condition_variable::notify_one()
, but it's not wrong in the sense that it's still well defined behavior and not an error.
However, it might be a "pessimization" since whatever waiting thread is made runnable (if any) will immediately try to acquire the lock that the notifying thread holds. I think it's a good rule of thumb to avoid holding the lock associated with a condition variable while calling notify_one()
or notify_all()
. See Pthread Mutex: pthread_mutex_unlock() consumes lots of time for an example where releasing a lock before calling the pthread equivalent of notify_one()
improved performance measurably.
Keep in mind that the lock()
call in the while
loop is necessary at some point, because the lock needs to be held during the while (!done)
loop condition check. But it doesn't need to be held for the call to notify_one()
.
2016-02-27: Large update to address some questions in the comments about whether there's a race condition is the lock isn't help for the notify_one()
call. I know this update is late because the question was asked almost two years ago, but I'd like to address @Cookie's question about a possible race condition if the producer (signals()
in this example) calls notify_one()
just before the consumer (waits()
in this example) is able to call wait()
.
The key is what happens to i
- that's the object that actually indicates whether or not the consumer has "work" to do. The condition_variable
is just a mechanism to let the consumer efficiently wait for a change to i
.
The producer needs to hold the lock when updating i
, and the consumer must hold the lock while checking i
and calling condition_variable::wait()
(if it needs to wait at all). In this case, the key is that it must be the same instance of holding the lock (often called a critical section) when the consumer does this check-and-wait. Since the critical section is held when the producer updates i
and when the consumer checks-and-waits on i
, there is no opportunity for i
to change between when the consumer checks i
and when it calls condition_variable::wait()
. This is the crux for a proper use of condition variables.
The C++ standard says that condition_variable::wait() behaves like the following when called with a predicate (as in this case):
while (!pred())
wait(lock);
There are two situations that can occur when the consumer checks i
:
if
i
is 0 then the consumer callscv.wait()
, theni
will still be 0 when thewait(lock)
part of the implementation is called - the proper use of the locks ensures that. In this case the producer has no opportunity to call thecondition_variable::notify_one()
in itswhile
loop until after the consumer has calledcv.wait(lk, []{return i == 1;})
(and thewait()
call has done everything it needs to do to properly 'catch' a notify -wait()
won't release the lock until it has done that). So in this case, the consumer cannot miss the notification.if
i
is already 1 when the consumer callscv.wait()
, thewait(lock)
part of the implementation will never be called because thewhile (!pred())
test will cause the internal loop to terminate. In this situation it doesn't matter when the call to notify_one() occurs - the consumer will not block.
The example here does have the additional complexity of using the done
variable to signal back to the producer thread that the consumer has recognized that i == 1
, but I don't think this changes the analysis at all because all of the access to done
(for both reading and modifying) are done while in the same critical sections that involve i
and the condition_variable
.
If you look at the question that @eh9 pointed to, Sync is unreliable using std::atomic and std::condition_variable, you will see a race condition. However, the code posted in that question violates one of the fundamental rules of using a condition variable: It does not hold a single critical section when performing a check-and-wait.
In that example, the code looks like:
if (--f->counter == 0) // (1)
// we have zeroed this fence's counter, wake up everyone that waits
f->resume.notify_all(); // (2)
else
{
unique_lock<mutex> lock(f->resume_mutex);
f->resume.wait(lock); // (3)
}
You will notice that the wait()
at #3 is performed while holding f->resume_mutex
. But the check for whether or not the wait()
is necessary at step #1 is not done while holding that lock at all (much less continuously for the check-and-wait), which is a requirement for proper use of condition variables). I believe that the person who has the problem with that code snippet thought that since f->counter
was a std::atomic
type this would fulfill the requirement. However, the atomicity provided by std::atomic
doesn't extend to the subsequent call to f->resume.wait(lock)
. In this example, there is a race between when f->counter
is checked (step #1) and when the wait()
is called (step #3).
That race does not exist in this question's example.
As others have pointed out, you do not need to be holding the lock when calling notify_one()
, in terms of race conditions and threading-related issues. However, in some cases, holding the lock may be required to prevent the condition_variable
from getting destroyed before notify_one()
is called. Consider the following example:
thread t;
void foo() {
std::mutex m;
std::condition_variable cv;
bool done = false;
t = std::thread([&]() {
{
std::lock_guard<std::mutex> l(m); // (1)
done = true; // (2)
} // (3)
cv.notify_one(); // (4)
}); // (5)
std::unique_lock<std::mutex> lock(m); // (6)
cv.wait(lock, [&done]() { return done; }); // (7)
}
void main() {
foo(); // (8)
t.join(); // (9)
}
Assume there is a context switch to the newly created thread t
after we created it but before we start waiting on the condition variable (somewhere between (5) and (6)). The thread t
acquires the lock (1), sets the predicate variable (2) and then releases the lock (3). Assume there is another context switch right at this point before notify_one()
(4) is executed. The main thread acquires the lock (6) and executes line (7), at which point the predicate returns true
and there is no reason to wait, so it releases the lock and continues. foo
returns (8) and the variables in its scope (including cv
) are destroyed. Before thread t
could join the main thread (9), it has to finish its execution, so it continues from where it left off to execute cv.notify_one()
(4), at which point cv
is already destroyed!
The possible fix in this case is to keep holding the lock when calling notify_one
(i.e. remove the scope ending in line (3)). By doing so, we ensure that thread t
calls notify_one
before cv.wait
can check the newly set predicate variable and continue, since it would need to acquire the lock, which t
is currently holding, to do the check. So, we ensure that cv
is not accessed by thread t
after foo
returns.
To summarize, the problem in this specific case is not really about threading, but about the lifetimes of the variables captured by reference. cv
is captured by reference via thread t
, hence you have to make sure cv
stays alive for the duration of the thread's execution. The other examples presented here do not suffer from this issue, because condition_variable
and mutex
objects are defined in the global scope, hence they are guaranteed to be kept alive until the program exits.
Situation
Using vc10 and Boost 1.56 I implemented a concurrent queue pretty much like this blog post suggests. The author unlocks the mutex to minimize contention, i.e., notify_one()
is called with the mutex unlocked:
void push(const T& item)
{
std::unique_lock<std::mutex> mlock(mutex_);
queue_.push(item);
mlock.unlock(); // unlock before notificiation to minimize mutex contention
cond_.notify_one(); // notify one waiting thread
}
Unlocking the mutex is backed by an example in the Boost documentation:
void prepare_data_for_processing()
{
retrieve_data();
prepare_data();
{
boost::lock_guard<boost::mutex> lock(mut);
data_ready=true;
}
cond.notify_one();
}
Problem
Still this led to the following erratic behaviour:
- while
notify_one()
has not been called yetcond_.wait()
can still be interrupted viaboost::thread::interrupt()
- once
notify_one()
was called for the first timecond_.wait()
deadlocks; the wait cannot be ended byboost::thread::interrupt()
orboost::condition_variable::notify_*()
anymore.
Solution
Removing the line mlock.unlock()
made the code work as expected (notifications and interrupts end the wait). Note that notify_one()
is called with the mutex still locked, it is unlocked right afterwards when leaving the scope:
void push(const T& item)
{
std::lock_guard<std::mutex> mlock(mutex_);
queue_.push(item);
cond_.notify_one(); // notify one waiting thread
}
That means that at least with my particular thread implementation the mutex must not be unlocked before calling boost::condition_variable::notify_one()
, although both ways seem correct.