28. Low-latency CV
In a feed handler handing off work to a risk thread, we want minimal latency without spinning. A missed wakeup or data race can stall processing or introduce nondeterministic delays. Analyze the following synchronization snippet for correctness and latency implications.
std::mutex m; std::condition_variable cv; bool ready=false;
void produce(){ ready=true; cv.notify_one(); }
void consume(){ std::unique_lock<std::mutex> lk(m);
cv.wait(lk,[&]{ return ready; }); ready=false; }
Part 1.
What is wrong with this snippet from a correctness standpoint, and how would you minimally fix it to avoid UB and missed wakeups?
Part 2.
(1) Why must the predicate be protected by the same mutex?
(2) Is notifying while holding the lock preferable or harmful?
(3) Could ready be std::atomic<bool> without the mutex? Trade-offs.
(4) How do spurious wakeups influence the predicate’s design and loop structure?
(5) How to avoid lost wakeups during bursts of producer signals?
Answer
Answer (Part 1)
ready is written without holding m while the consumer reads it under m, causing a data race (UB). Modify the shared state under the same mutex, and then notify. Example fix: update ready with std::lock_guard<std::mutex> g(m); ready=true; in produce, and keep consume’s wait(lk, pred) which already handles spurious wakeups. Notifying either under the lock or immediately after unlocking is correct; unlocking first often reduces contention. Also keep ready=false; while still holding lk after wake to preserve invariants.
Answer (Part 2)
(1) It prevents data races and establishes happens-before with wait. The predicate must be read/written under the same lock.
(2) Both are correct if the predicate is locked. Unlock-before-notify often reduces contention; notify-under-lock can reduce unnecessary wakeups.
(3) Atomics remove the data race, but you still need a mutex for wait. Atomics plus busy-wait can lower latency but burn CPU.
(4) Always recheck the condition in a loop. Use wait(lk, pred) so spurious wakeups simply continue waiting.
(5) Use a counted state (e.g., semaphore-like counter) instead of a boolean. Update under lock and notify after changing the counter.