Introduction
The Condvar
(Condition Variable) class in Rust provides a way for threads to synchronize the execution of certain actions. It is typically used in conjunction with a Mutex
to allow threads to wait for some condition to be met before proceeding. This is particularly useful for scenarios where one thread needs to wait for another thread to finish a specific task or reach a particular state.
Using Condvar
The Condvar
class can be used to block a thread until a certain condition is met. Here is a basic example:
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();
});
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
started = cvar.wait(started).unwrap();
}
println!("Condition met, proceeding...");
}
// Output: Condition met, proceeding...
Key Methods
Below are some of the key methods exposed by the CondVar
class:
new
Creates a new Condvar
instance.
use std::sync::Condvar;
fn main() {
let cvar = Condvar::new();
println!("Condition variable created.");
}
// Output: Condition variable created.
wait
Blocks the current thread until the condition variable is notified. The thread will release the associated Mutex
and re-acquire it once notified.
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();
});
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
started = cvar.wait(started).unwrap();
}
println!("Condition met, proceeding...");
}
// Output: Condition met, proceeding...
wait_timeout
Blocks the current thread until the condition variable is notified or the specified duration has elapsed.
use std::sync::{Arc, Mutex, Condvar};
use std::time::Duration;
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();
});
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
let result = cvar.wait_timeout(started, Duration::from_secs(1)).unwrap();
if *result.0 {
println!("Condition met, proceeding...");
} else {
println!("Timeout elapsed, condition not met.");
}
}
// Output: Condition met, proceeding...
notify_one
Wakes up one thread that is waiting on this condition variable.
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();
});
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
started = cvar.wait(started).unwrap();
}
println!("Condition met, proceeding...");
}
// Output: Condition met, proceeding...
notify_all
Wakes up all threads that are waiting on this condition variable.
use std::sync::{Arc, Mutex, Condvar};
use std::thread;
fn main() {
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair_clone1 = Arc::clone(&pair);
let pair_clone2 = Arc::clone(&pair);
thread::spawn(move || {
let (lock, cvar) = &*pair_clone1;
let mut started = lock.lock().unwrap();
*started = true;
cvar.notify_all();
});
thread::spawn(move || {
let (lock, cvar) = &*pair_clone2;
let mut started = lock.lock().unwrap();
while !*started {
started = cvar.wait(started).unwrap();
}
println!("Thread 2 proceeding...");
});
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
started = cvar.wait(started).unwrap();
}
println!("Thread 1 proceeding...");
}
// Output:
// Thread 2 proceeding...
// Thread 1 proceeding...
Example Usage
Example 1: Basic Usage
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();
});
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
started = cvar.wait(started).unwrap();
}
println!("Condition met, proceeding...");
}
// Output: Condition met, proceeding...
Example 2: Using notify_one
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();
});
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
started = cvar.wait(started).unwrap();
}
println!("Condition met, proceeding...");
}
// Output: Condition met, proceeding...
Example 3: Using notify_all
use std::sync::{Arc, Mutex, Condvar};
use std::thread;
fn main() {
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair_clone1 = Arc::clone(&pair);
let pair_clone2 = Arc::clone(&pair);
thread::spawn(move || {
let (lock, cvar) = &*pair_clone1;
let mut started = lock.lock().unwrap();
*started = true;
cvar.notify_all();
});
thread::spawn(move || {
let (lock, cvar) = &*pair_clone2;
let mut started = lock.lock().unwrap();
while !*started {
started = cvar.wait(started).unwrap();
}
println!("Thread 2 proceeding...");
});
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
started = cvar.wait(started).unwrap();
}
println!("Thread 1 proceeding...");
}
// Output:
// Thread 2 proceeding...
// Thread 1 proceeding...
Example 4: Using wait_timeout
use std::sync::{Arc, Mutex, Condvar};
use std::time::Duration;
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();
});
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
let result = cvar.wait_timeout(started, Duration::from_secs(1)).unwrap();
if *result.0 {
println!("Condition met, proceeding...");
} else {
println!("Timeout elapsed, condition not met.");
}
}
// Output: Condition met, proceeding...
Example 5: Using Condvar with a Counter
use std::sync::{Arc, Mutex, Condvar};
use std::thread;
fn main() {
let counter = Arc::new((Mutex::new(0), Condvar::new()));
let counter_clone = Arc::clone(&counter);
thread::spawn(move || {
let (lock, cvar) = &*counter_clone;
let mut count = lock.lock().unwrap();
*count += 1;
cvar.notify_one();
});
let (lock, cvar) = &*counter;
let mut count = lock.lock().unwrap();
while *count == 0 {
count = cvar.wait(count).unwrap();
}
println!("Counter: {}", *count);
}
// Output: Counter: 1
Considerations
- Using a
Condvar
requires an associatedMutex
. Ensure that theMutex
is locked before callingwait
,wait_timeout
, or any notify method. - Avoid spurious wakeups by always checking the condition in a loop after
wait
returns. - Consider using
notify_all
if multiple threads might be waiting on the same condition variable.
See Also
- Mutex - A mutual exclusion primitive useful for protecting shared data.
- RwLock - A reader-writer lock for allowing concurrent reads or exclusive writes.
- Arc - An atomic reference-counted smart pointer for shared ownership across threads.
- AtomicUsize - A thread-safe atomic integer type.