Loading page…
Rust walkthroughs
Loading page…
parking_lot::Condvar compare to std::sync::Condvar in terms of API ergonomics?parking_lot::Condvar provides a more ergonomic API that avoids the common pitfalls of std::sync::Condvar by not requiring a mutex guard to be passed to wait operations, using simpler timeout handling, and eliminating spurious wakeups in practice. The standard library's Condvar requires the MutexGuard to be passed to wait() so it can atomically unlock and wait, while parking_lot::Condvar simply unlocks the Mutex directly and doesn't require holding a guard. This design difference reduces boilerplate, prevents incorrect usage patterns, and makes the API more approachable while maintaining the same fundamental synchronization guarantees.
use std::sync::{Arc, Mutex, Condvar};
use std::thread;
fn main() {
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair_clone = Arc::clone(&pair);
thread::spawn(move || {
let (lock, cvar) = &*pair_clone;
let mut started = lock.lock().unwrap();
*started = true;
cvar.notify_one(); // Notify waiting thread
});
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
// Must pass the guard to wait()
while !*started {
started = cvar.wait(started).unwrap();
}
println!("Thread has started");
}std::sync::Condvar::wait() requires the mutex guard as a parameter.
use parking_lot::{Mutex, Condvar};
use std::sync::Arc;
use std::thread;
fn main() {
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair_clone = Arc::clone(&pair);
thread::spawn(move || {
let (lock, cvar) = &*pair_clone;
let mut started = lock.lock();
*started = true;
cvar.notify_one(); // Notify waiting thread
});
let (lock, cvar) = &*pair;
let mut started = lock.lock();
// No guard parameter needed
while !*started {
cvar.wait(&mut started);
}
println!("Thread has started");
}parking_lot::Condvar::wait() takes a mutable reference to the guard instead.
use std::sync::{Mutex, Condvar};
fn std_pattern() {
let mutex = Mutex::new(42);
let cvar = Condvar::new();
let guard = mutex.lock().unwrap();
// wait() takes ownership of the guard
// and returns it when signaled
let guard = cvar.wait(guard).unwrap();
// The pattern requires re-binding because wait() returns the guard
}std::sync::Condvar::wait() consumes and returns the guard.
use parking_lot::{Mutex, Condvar};
fn parking_lot_pattern() {
let mutex = Mutex::new(42);
let cvar = Condvar::new();
let mut guard = mutex.lock();
// wait() takes a reference
cvar.wait(&mut guard);
// Guard is still valid, no re-binding needed
}parking_lot::Condvar::wait() borrows the guard mutably.
use std::sync::{Mutex, Condvar};
use std::time::Duration;
fn std_timeout() {
let mutex = Mutex::new(false);
let cvar = Condvar::new();
let guard = mutex.lock().unwrap();
// wait_timeout() returns (MutexGuard, WaitTimeoutResult)
let (guard, result) = cvar.wait_timeout(guard, Duration::from_secs(1)).unwrap();
if result.timed_out() {
println!("Timed out");
}
// Guard is available again
let _value = *guard;
}std::sync::Condvar::wait_timeout() returns a tuple of the guard and a timeout result.
use parking_lot::{Mutex, Condvar};
use std::time::Duration;
fn parking_lot_timeout() {
let mutex = Mutex::new(false);
let cvar = Condvar::new();
let mut guard = mutex.lock();
// wait_timeout() returns bool for timeout status
let timed_out = cvar.wait_timeout(&mut guard, Duration::from_secs(1));
if timed_out {
println!("Timed out");
}
// Guard is still valid
let _value = *guard;
}parking_lot::Condvar::wait_timeout() returns a simple bool.
use std::sync::{Mutex, Condvar};
use std::time::{Duration, Instant};
fn std_variants() {
let mutex = Mutex::new(0);
let cvar = Condvar::new();
let guard = mutex.lock().unwrap();
// Basic wait
let guard = cvar.wait(guard).unwrap();
// Wait with duration timeout
let (guard, result) = cvar.wait_timeout(guard, Duration::from_secs(1)).unwrap();
// Wait with instant deadline
let deadline = Instant::now() + Duration::from_secs(1);
let (guard, result) = cvar.wait_timeout_until(guard, deadline).unwrap();
}std::sync::Condvar provides three wait variants.
use parking_lot::{Mutex, Condvar};
use std::time::{Duration, Instant};
fn parking_lot_variants() {
let mutex = Mutex::new(0);
let cvar = Condvar::new();
let mut guard = mutex.lock();
// Basic wait
cvar.wait(&mut guard);
// Wait with duration
let timed_out = cvar.wait_timeout(&mut guard, Duration::from_secs(1));
// Wait until deadline
let deadline = Instant::now() + Duration::from_secs(1);
let timed_out = cvar.wait_until(&mut guard, deadline);
}parking_lot::Condvar uses consistent reference-based API for all variants.
use std::sync::{Mutex, Condvar};
fn std_wait_while() {
let mutex = Mutex::new(0);
let cvar = Condvar::new();
let guard = mutex.lock().unwrap();
// wait_while() waits while predicate returns true
let guard = cvar.wait_while(guard, |value| *value < 10).unwrap();
// Guard now has value >= 10
println!("Value: {}", *guard);
}std::sync::Condvar::wait_while() simplifies the common wait-loop pattern.
use parking_lot::{Mutex, Condvar};
fn parking_lot_wait_while() {
let mutex = Mutex::new(0);
let cvar = Condvar::new();
let mut guard = mutex.lock();
// wait_while() takes reference and predicate
cvar.wait_while(&mut guard, |value| *value < 10);
// Guard now has value >= 10
println!("Value: {}", *guard);
}parking_lot::Condvar::wait_while() uses the same reference pattern.
use std::sync::{Mutex, Condvar};
use std::thread;
use std::sync::Arc;
fn std_notify() {
let mutex = Mutex::new(false);
let cvar = Condvar::new();
let arc = Arc::new((mutex, cvar));
let arc_clone = Arc::clone(&arc);
thread::spawn(move || {
let (lock, cvar) = &*arc_clone;
let mut guard = lock.lock().unwrap();
*guard = true;
// Wake one waiting thread
cvar.notify_one();
});
let (lock, cvar) = &*arc;
let mut guard = lock.lock().unwrap();
while !*guard {
guard = cvar.wait(guard).unwrap();
}
}Both libraries provide notify_one() and notify_all().
use parking_lot::{Mutex, Condvar};
use std::thread;
use std::sync::Arc;
fn parking_lot_notify() {
let mutex = Mutex::new(false);
let cvar = Condvar::new();
let arc = Arc::new((mutex, cvar));
let arc_clone = Arc::clone(&arc);
thread::spawn(move || {
let (lock, cvar) = &*arc_clone;
let mut guard = lock.lock();
*guard = true;
// Wake one waiting thread
cvar.notify_one();
});
let (lock, cvar) = &*arc;
let mut guard = lock.lock();
while !*guard {
cvar.wait(&mut guard);
}
}The notification methods are identical in semantics.
use std::sync::{Mutex, Condvar};
use std::thread;
fn std_poisoning() {
let mutex = Mutex::new(0);
let cvar = Condvar::new();
// If a thread panics while holding the mutex,
// subsequent lock() returns Err(PoisonError)
let guard = mutex.lock().unwrap(); // May panic if poisoned
// wait() also returns Result because it reacquires the lock
let guard = cvar.wait(guard).unwrap(); // May return Err
}std::sync::Condvar operations return Result for poison handling.
use parking_lot::{Mutex, Condvar};
fn parking_lot_no_poison() {
let mutex = Mutex::new(0);
let cvar = Condvar::new();
// parking_lot doesn't have poisoning
// lock() always succeeds
let mut guard = mutex.lock();
// wait() always succeeds, no Result
cvar.wait(&mut guard);
}parking_lot::Condvar doesn't use poison errors.
use std::sync::{Mutex, Condvar};
use std::thread;
fn std_poison_example() {
let mutex = Mutex::new(vec![1, 2, 3]);
let cvar = Condvar::new();
let arc = Arc::new((mutex, cvar));
let arc_clone = Arc::clone(&arc);
let handle = thread::spawn(move || {
let (lock, cvar) = &*arc_clone;
let mut data = lock.lock().unwrap();
data.push(4);
panic!("Thread panicked!"); // Mutex is now poisoned
});
handle.join().unwrap_err(); // Catch the panic
let (lock, _) = &*arc;
match lock.lock() {
Ok(_) => println!("Mutex recovered"),
Err(e) => {
// Can still access data with into_inner()
let data = e.into_inner();
println!("Data after panic: {:?}", data); // [1, 2, 3, 4]
}
}
}Standard library poisoning allows detecting invariant violations.
use parking_lot::{Mutex, Condvar};
use std::thread;
use std::sync::Arc;
fn parking_lot_no_poison_example() {
let mutex = Mutex::new(vec![1, 2, 3]);
let cvar = Condvar::new();
let arc = Arc::new((mutex, cvar));
let arc_clone = Arc::clone(&arc);
let handle = thread::spawn(move || {
let (lock, cvar) = &*arc_clone;
let mut data = lock.lock();
data.push(4);
panic!("Thread panicked!");
});
handle.join().unwrap_err();
let (lock, _) = &*arc;
let data = lock.lock(); // Always succeeds
println!("Data after panic: {:?}", *data); // [1, 2, 3, 4]
}parking_lot continues after panic, which may or may not be desirable.
use std::sync::{Mutex, Condvar};
fn std_spurious_handling() {
let mutex = Mutex::new(false);
let cvar = Condvar::new();
let guard = mutex.lock().unwrap();
// MUST use a loop to handle spurious wakeups
let mut guard = guard;
while !*guard {
guard = cvar.wait(guard).unwrap();
}
}Both require loop-based predicate checking for correctness.
use parking_lot::{Mutex, Condvar};
fn parking_lot_spurious_handling() {
let mutex = Mutex::new(false);
let cvar = Condvar::new();
let mut guard = mutex.lock();
// Same requirement for predicate checking
while !*guard {
cvar.wait(&mut guard);
}
}The fundamental requirement for loop-based waiting is identical.
use std::sync::{Mutex, Condvar};
use std::time::Instant;
fn std_benchmark() {
let mutex = Mutex::new(0);
let cvar = Condvar::new();
let start = Instant::now();
let guard = mutex.lock().unwrap();
// Standard library has different fairness characteristics
// May not be as optimized for all cases
println!("Lock acquired in {:?}", start.elapsed());
}Standard library mutex/condvar prioritizes correctness over raw performance.
use parking_lot::{Mutex, Condvar};
use std::time::Instant;
fn parking_lot_benchmark() {
let mutex = Mutex::new(0);
let cvar = Condvar::new();
let start = Instant::now();
let guard = mutex.lock();
// parking_lot is optimized for common cases
// Generally better performance on contended locks
println!("Lock acquired in {:?}", start.elapsed());
}parking_lot uses more efficient internal implementations.
use std::sync::{Mutex, Condvar};
// std::sync::Condvar is designed for std::sync::Mutex
// Can't use with parking_lot::Mutex
fn std_type_coupling() {
let mutex = Mutex::new(0);
let cvar = Condvar::new();
// This pairing is enforced by the type system
let guard = mutex.lock().unwrap();
let _guard = cvar.wait(guard).unwrap();
}std::sync::Condvar is tightly coupled with std::sync::Mutex.
use parking_lot::{Mutex, Condvar};
// parking_lot::Condvar works with parking_lot::Mutex
// Also has consistent API with other parking_lot types
fn parking_lot_type_coupling() {
let mutex = Mutex::new(0);
let cvar = Condvar::new();
let mut guard = mutex.lock();
cvar.wait(&mut guard);
}parking_lot::Condvar is designed to work with parking_lot::Mutex.
| Aspect | std::sync::Condvar | parking_lot::Condvar |
|--------|---------------------|----------------------|
| Guard parameter | Consumes and returns | Borrows mutably |
| Wait return type | Result<MutexGuard> | Nothing (unit) |
| Timeout return | (Guard, WaitTimeoutResult) | bool |
| Poisoning | Returns Result | Never poisons |
| API complexity | Higher (Result handling) | Lower (direct) |
| Guard re-binding | Required after wait() | Not required |
| Spurious wakeups | Possible | Possible |
| Mutex coupling | std::sync::Mutex only | parking_lot::Mutex only |
The ergonomic differences stem from fundamental design philosophies:
Guard handling: std::sync::Condvar requires passing the MutexGuard by value because it must atomically release the lock and wait, then reacquire before returning. This creates the awkward let guard = cvar.wait(guard)? pattern. parking_lot::Condvar achieves the same atomicity through its mutex implementation, allowing it to borrow the guard instead. The mutable reference pattern cvar.wait(&mut guard) is more natural and doesn't require re-binding.
Poisoning philosophy: The standard library treats mutex poisoning as a recoverable error, so every operation returns Result. This adds .unwrap() noise throughout condvar code. parking_lot takes the position that poisoning is rare and usually indicates unrecoverable invariant violation, so it doesn't poison at all. The result is cleaner code but less visibility into panic-induced state corruption.
Timeout simplicity: std::sync::Condvar::wait_timeout() returns a tuple of the guard and a WaitTimeoutResult struct. parking_lot::Condvar::wait_timeout() returns just a bool. The simpler return type covers most use cases: you check if it timed out and continue with the guard you already have.
Key insight: parking_lot::Condvar's ergonomics come from not requiring ownership transfer of the guard. This one design choice cascades into simpler return types, no Result wrapping, and no re-binding after wait. The trade-off is losing poisoning detection, which is valuable for some systems and unnecessary for others. Choose std::sync::Condvar when you need poisoning semantics for invariant detection; choose parking_lot::Condvar for cleaner code in systems where panics are treated as unrecoverable anyway.