Loading pageâŚ
Rust walkthroughs
Loading pageâŚ
parking_lot::Condvar::notify_all differ from std::sync::Condvar in terms of spurious wakeups handling?Both parking_lot::Condvar and std::sync::Condvar require explicit condition checks in a loop to handle spurious wakeupsâneither eliminates spurious wakeups, which are permitted by the underlying OS primitivesâbut parking_lot::Condvar provides a more ergonomic API through its wait_until method that encapsulates the loop pattern, while std::sync::Condvar requires manual loop construction. The key difference is API ergonomics rather than fundamental behavior: parking_lot acknowledges that spurious wakeups are unavoidable and provides built-in support for the correct wait-loop pattern, whereas std::sync::Condvar leaves the loop implementation entirely to the programmer. Both implementations may wake threads spuriously due to OS-level futex semantics.
use std::sync::{Condvar, Mutex};
fn std_condvar_wait() {
let pair = Mutex::new((false, Condvar::new()));
let (lock, cvar) = &*pair;
// Standard pattern: must use loop to handle spurious wakeups
let mut guard = lock.lock().unwrap();
while !guard.0 {
guard = cvar.wait(guard).unwrap();
}
// Condition is now true
}The standard library requires a manual loop because spurious wakeups can occur.
use parking_lot::{Condvar, Mutex};
fn parking_lot_wait_until() {
let mutex = Mutex::new(false);
let cvar = Condvar::new();
// parking_lot provides wait_until that handles the loop internally
let mut guard = mutex.lock();
cvar.wait_until(&mut guard, |state| *state);
// Condition is now true
}parking_lot::Condvar::wait_until encapsulates the spurious-wakeup-handling loop.
use std::sync::{Condvar as StdCondvar, Mutex as StdMutex};
use parking_lot::{Condvar as PlCondvar, Mutex as PlMutex};
fn both_need_loops() {
// std::sync::Condvar
let mutex = StdMutex::new(false);
let cvar = StdCondvar::new();
let mut guard = mutex.lock().unwrap();
// MUST use loop - spurious wakeups possible
while !*guard {
guard = cvar.wait(guard).unwrap();
}
// parking_lot::Condvar with wait()
let mutex = PlMutex::new(false);
let cvar = PlCondvar::new();
let mut guard = mutex.lock();
// MUST use loop with wait() too
while !*guard {
cvar.wait(&mut guard);
}
// parking_lot::Condvar with wait_until()
let mut guard = mutex.lock();
// wait_until handles loop internally
cvar.wait_until(&mut guard, |state| *state);
}parking_lot::Condvar::wait() still requires manual loops; wait_until() encapsulates it.
use std::sync::{Condvar, Mutex};
fn spurious_wakeup_explanation() {
// A spurious wakeup occurs when:
// - Thread is waiting on condvar
// - Thread wakes up WITHOUT being notified
// - This is allowed by POSIX and common on Linux
// Causes include:
// - OS-level futex implementation details
// - Signal handling
// - Performance optimizations in kernel
// Example of incorrect code (vulnerable to spurious wakeups):
let mutex = Mutex::new(false);
let cvar = Condvar::new();
let mut guard = mutex.lock().unwrap();
// WRONG: if statement instead of while loop
if !*guard {
guard = cvar.wait(guard).unwrap();
}
// BUG: May proceed even though condition is false
// if spurious wakeup occurred
// CORRECT: while loop
while !*guard {
guard = cvar.wait(guard).unwrap();
}
// SAFE: Always re-checks condition after waking
}Spurious wakeups are a fact of condvar semantics; neither library eliminates them.
use parking_lot::{Condvar, Mutex};
fn wait_until_internals() {
let mutex = Mutex::new(0);
let cvar = Condvar::new();
// wait_until is equivalent to:
fn wait_until_manual<T>(
cvar: &Condvar,
guard: &mut parking_lot::MutexGuard<'_, T>,
condition: impl FnMut(&T) -> bool,
) {
while !condition(&*guard) {
cvar.wait(guard);
}
}
// park_lot's implementation does exactly this
let mut guard = mutex.lock();
cvar.wait_until(&mut guard, |value| *value > 0);
}wait_until is syntactic sugar for the standard loop pattern.
use std::sync::{Condvar as StdCondvar, Mutex as StdMutex};
use parking_lot::{Condvar as PlCondvar, Mutex as PlMutex};
fn notify_all_both() {
// Both libraries have notify_all
// Both wake ALL waiting threads
// std::sync::Condvar
let mutex = StdMutex::new(false);
let cvar = StdCondvar::new();
{
let _guard = mutex.lock().unwrap();
cvar.notify_all();
}
// parking_lot::Condvar
let mutex = PlMutex::new(false);
let cvar = PlCondvar::new();
{
let _guard = mutex.lock();
cvar.notify_all();
}
// Key insight: notify_all does NOT cause spurious wakeups
// Spurious wakeups come from the OS, not from notify_all
// Both notify_all implementations behave the same
}notify_all itself doesn't cause spurious wakeups; the OS does.
use parking_lot::{Condvar, Mutex};
fn notify_differences() {
let mutex = Mutex::new(vec
![]);
let cvar = Condvar::new();
// notify_one: wakes ONE waiting thread
cvar.notify_one();
// notify_all: wakes ALL waiting threads
cvar.notify_all();
// When to use each:
// - notify_one: When only one thread can make progress
// (e.g., single resource becomes available)
// - notify_all: When multiple threads might make progress
// (e.g., state change affects many wait conditions)
// Spurious wakeups affect both notify_one and notify_all
// Both still require condition loops
}The choice between notify_one and notify_all is about semantics, not spurious wakeups.
use std::sync::{Condvar, Mutex};
use std::time::{Duration, Instant};
fn timeout_std() {
let mutex = Mutex::new(false);
let cvar = Condvar::new();
let mut guard = mutex.lock().unwrap();
// std::sync::Condvar: wait_timeout returns result
let result = cvar.wait_timeout(guard, Duration::from_secs(1)).unwrap();
guard = result.0;
let timed_out = result.1;
// STILL need loop for spurious wakeups
let start = Instant::now();
while !*guard && start.elapsed() < Duration::from_secs(1) {
let result = cvar.wait_timeout(guard, Duration::from_secs(1)).unwrap();
guard = result.0;
}
}
fn timeout_parking_lot() {
use parking_lot::{Condvar, Mutex};
let mutex = Mutex::new(false);
let cvar = Condvar::new();
let mut guard = mutex.lock();
// parking_lot: wait_until with timeout
let result = cvar.wait_until(&mut guard, |state| *state, Duration::from_secs(1));
// Returns true if condition met, false if timeout
}parking_lot::Condvar::wait_until with timeout is cleaner than manual timeout loops.
use std::sync::{Condvar as StdC, Mutex as StdM};
use parking_lot::{Condvar as PlC, Mutex as PlM};
fn api_comparison() {
// std::sync::Condvar:
// - wait(guard) -> LockResult<MutexGuard>
// - wait_timeout(guard, dur) -> WaitTimeoutResult
// - notify_one()
// - notify_all()
// Manual loop required:
let mutex = StdM::new(0);
let cvar = StdC::new();
let mut guard = mutex.lock().unwrap();
while *guard == 0 {
guard = cvar.wait(guard).unwrap();
}
// parking_lot::Condvar:
// - wait(guard)
// - wait_until(guard, condition) <-- Key addition
// - wait_until(guard, condition, timeout)
// - notify_one()
// - notify_all()
// Built-in condition handling:
let mutex = PlM::new(0);
let cvar = PlC::new();
let mut guard = mutex.lock();
cvar.wait_until(&mut guard, |v| *v > 0);
}parking_lot adds wait_until for ergonomic condition checking.
use std::sync::{Condvar, Mutex};
use parking_lot::{Condvar as PlCondvar, Mutex as PlMutex};
fn poisoning_differences() {
// std::sync::Condvar: Handles mutex poisoning
let mutex = Mutex::new(0);
let cvar = Condvar::new();
// If mutex is poisoned, wait returns Err
let guard = mutex.lock().unwrap();
let result = cvar.wait(guard);
// Result::LockResult must be handled
// parking_lot::Condvar: No poisoning (mutex is "unpoisonable")
let mutex = PlMutex::new(0);
let cvar = PlCondvar::new();
let mut guard = mutex.lock();
cvar.wait(&mut guard);
// No Result to handle - parking_lot mutexes don't poison
}parking_lot eliminates poisoning, simplifying condvar code.
use parking_lot::{Condvar, Mutex};
fn complex_conditions() {
struct State {
queue: Vec<i32>,
shutdown: bool,
max_size: usize,
}
let mutex = Mutex::new(State {
queue: Vec::new(),
shutdown: false,
max_size: 10,
});
let cvar = Condvar::new();
// Complex condition with wait_until
let mut guard = mutex.lock();
cvar.wait_until(&mut guard, |state| {
state.queue.len() > 0 || state.shutdown
});
// Multiple conditions
cvar.wait_until(&mut guard, |state| {
state.queue.len() >= state.max_size || state.shutdown
});
}wait_until handles arbitrary predicate complexity cleanly.
use std::sync::{Condvar, Mutex};
use std::time::Duration;
fn spurious_frequency() {
// How often do spurious wakeups occur?
// - Rarely on most systems
// - More common under load
// - Implementation dependent
// But: "rarely" doesn't mean "never"
// Correct code MUST handle them
// This loop might work most of the time:
let mutex = Mutex::new(false);
let cvar = Condvar::new();
let mut guard = mutex.lock().unwrap();
// DON'T DO THIS - vulnerable to spurious wakeups
if !*guard {
cvar.wait(guard).unwrap();
}
// This might work 99.9% of the time
// But that 0.1% is a hard-to-debug race condition
// ALWAYS use while loop or wait_until:
while !*guard {
cvar.wait(guard).unwrap();
}
}Spurious wakeups are rare but must be handled for correctness.
// std::sync::Condvar (simplified view):
// - Uses OS futex (Linux) or CONDITION_VARIABLE (Windows)
// - wait() releases mutex and blocks atomically
// - Wakes reacquire mutex
// - Spurious wakeups from OS semantics
// parking_lot::Condvar (simplified view):
// - Also uses OS primitives
// - wait() behavior similar to std
// - wait_until() adds loop around wait()
// - Same spurious wakeup semantics from OS
fn implementation_notes() {
// Both ultimately use OS-level primitives
// Both are subject to OS-level spurious wakeups
// Neither can eliminate spurious wakeups
// The difference is only in API ergonomics:
// - std: Manual loop required
// - parking_lot: wait_until provides loop
}The underlying semantics are identical; only the API differs.
use std::sync::{Condvar, Mutex};
use parking_lot::{Condvar as PlCondvar, Mutex as PlMutex};
fn correct_patterns() {
// CORRECT with std::sync::Condvar:
let mutex = Mutex::new(0);
let cvar = Condvar::new();
let mut guard = mutex.lock().unwrap();
while *guard == 0 {
guard = cvar.wait(guard).unwrap();
}
// CORRECT with parking_lot::Condvar::wait():
let mutex = PlMutex::new(0);
let cvar = PlCondvar::new();
let mut guard = mutex.lock();
while *guard == 0 {
cvar.wait(&mut guard);
}
// CORRECT with parking_lot::Condvar::wait_until():
let mutex = PlMutex::new(0);
let cvar = PlCondvar::new();
let mut guard = mutex.lock();
cvar.wait_until(&mut guard, |v| *v > 0);
// All three patterns correctly handle spurious wakeups
// wait_until() is just more ergonomic
}All correct patterns use a loop; wait_until encapsulates it.
use std::sync::{Condvar, Mutex};
use parking_lot::{Condvar as PlCondvar, Mutex as PlMutex};
fn common_mistakes() {
// MISTAKE 1: if instead of while
let mutex = Mutex::new(false);
let cvar = Condvar::new();
let mut guard = mutex.lock().unwrap();
// WRONG
if !*guard {
cvar.wait(guard).unwrap();
}
// CORRECT
while !*guard {
guard = cvar.wait(guard).unwrap();
}
// MISTAKE 2: Not re-acquiring guard before check
// (Both libraries handle this correctly)
// MISTAKE 3: Thinking wait_until eliminates spurious wakeups
// wait_until HANDLES them with internal loop, doesn't eliminate
// MISTAKE 4: Not holding lock when calling notify
{
let mutex = PlMutex::new(0);
let cvar = PlCondvar::new();
// WRONG (but may work)
cvar.notify_all();
*mutex.lock() = 1;
// CORRECT
let mut guard = mutex.lock();
*guard = 1;
cvar.notify_all();
}
}Always hold the lock when modifying shared state and notifying.
Spurious wakeup handling:
// Both std::sync::Condvar and parking_lot::Condvar:
// - Are subject to spurious wakeups from OS primitives
// - Require condition checks in loops
// - notify_all wakes all waiting threads
// - notify_one wakes one waiting thread
// Neither library eliminates spurious wakeups
// The difference is only in API ergonomicsKey differences:
// std::sync::Condvar:
// - Manual loop required: while !cond { cvar.wait(guard); }
// - Returns Result (poisoning)
// - Standard library, always available
// parking_lot::Condvar:
// - wait_until encapsulates loop
// - No poisoning (mutexes don't poison)
// - External dependency
// - Generally faster mutex implementationWhen to use each:
// Use std::sync::Condvar when:
// - Standard library only is preferred
// - Code needs to run without dependencies
// - Poisoning is desired behavior
// Use parking_lot::Condvar when:
// - wait_until ergonomics are valuable
// - Using parking_lot::Mutex already
// - Poisoning is not wanted
// - Performance of parking_lot is beneficialKey insight: parking_lot::Condvar::notify_all does not differ from std::sync::Condvar::notify_all in spurious wakeup handlingâboth are subject to OS-level spurious wakeups because they both use futex-like primitives underneath. The difference is that parking_lot::Condvar provides wait_until which encapsulates the standard condition-check loop pattern, making it harder to write incorrect code that forgets the loop. With std::sync::Condvar, developers must remember to write while !condition { cvar.wait(guard); }, while with parking_lot, cvar.wait_until(&mut guard, |state| condition) handles the loop internally. Both require the loop; parking_lot just makes it a single method call instead of a pattern you must implement correctly every time.