Consider the following scenario:

std::atomic<bool> condition{false};
std::mutex mutex;
std::condition_variable cv;
 
 
void waiter() {
	std::unique_lock lk(m);
	cv.wait(lk, []{ return condition; });
}
 
void setter() {
	condition = true;
	cv.notify_one();
}

The above code is race free when waiter and setter functions run on different threads. However, it is not correct. The Condition Variable can wait indefinitely.

See std condition_variable wait - cppreference.com;

Consider the case:

  1. Initialisation happens, and waiter() & setter() are executed on separate threads
  2. waiter() thread: Obtains a unique lock on the mutex.
  3. waiter() thread: cv.wait() checks that condition is false but context switches before it waits on cv. Note that it is still holding the lock.
  4. setter() thread: Sets condition to true and notifies cv. No thread is woken up because no thread is sleeping on it.
  5. waiter() thread: Starts to wait on cv, then releases the lock. This will end up waiting indefinitely or until a spurious wakeup happens.

Observe that if condition had been set after locking, the set could not have happened in between the functioning of cv.wait().

Info

When waiting on a variable through a condition variable, the variable has to be set only after acquiring the associated lock.