Rust Guide > Documentation > Concurrency > Condvar

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 associated Mutex. Ensure that the Mutex is locked before calling wait, 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.