BudiBadu Logo
Samplebadu

Rust by Example: Mutex and Condvar

Rust 1.75+

Coordinate thread access to shared resources. Learn to use Mutex for mutual exclusion and Condvar for thread signaling, essential primitives for implementing safe shared-state concurrency.

Code

use std::sync::{Arc, Mutex, Condvar};
use std::thread;

fn main() {
    // A Mutex (Mutual Exclusion) allows only one thread to access data at a time.
    // We wrap it in Arc (Atomic Reference Count) to share it across threads.
    let pair = Arc::new((Mutex::new(false), Condvar::new()));
    let pair2 = Arc::clone(&pair);

    thread::spawn(move || {
        let (lock, cvar) = &*pair2;
        
        // Lock the mutex to get access to the data
        let mut started = lock.lock().unwrap();
        
        println!("Worker thread: waiting for start signal...");
        
        // Wait until the boolean becomes true
        // cvar.wait() releases the lock and puts the thread to sleep
        // When it wakes up, it re-acquires the lock
        while !*started {
            started = cvar.wait(started).unwrap();
        }
        
        println!("Worker thread: working!");
    });

    let (lock, cvar) = &*pair;
    
    println!("Main thread: sleeping for 1 second...");
    thread::sleep(std::time::Duration::from_secs(1));
    
    println!("Main thread: sending signal.");
    let mut started = lock.lock().unwrap();
    *started = true;
    
    // Notify the waiting thread
    cvar.notify_one();
}

Explanation

A Mutex (Mutual Exclusion) is a synchronization primitive that prevents multiple threads from accessing the same shared data simultaneously. In Rust, Mutex<T> protects the data it holds. To access the data, you must lock the mutex, which returns a MutexGuard. This guard provides mutable access to the inner data and automatically releases the lock when it goes out of scope (RAII).

A Condvar (Condition Variable) is used to block a thread until a particular condition is met. It is designed to work with a Mutex. The wait method atomically releases the mutex lock and suspends the thread. When the thread is notified (via notify_one), it wakes up and re-acquires the lock.

Crucially, we use a while loop to check the condition (!*started) rather than an if statement. This protects against Spurious Wakeups, where the OS might wake the thread up even if no signal was sent. Re-checking the condition ensures the thread only proceeds when it is truly ready.

Code Breakdown

8
Arc::new(...). We wrap the (Mutex, Condvar) pair in an Arc because multiple threads need to own it. Arc provides shared ownership, while Mutex provides the interior mutability needed to change the boolean.
15
lock.lock().unwrap(). Acquires the lock. This blocks if another thread holds it. The unwrap handles "poisoning"—if a thread panics while holding the lock, the mutex is marked as poisoned to prevent other threads from seeing inconsistent data.
23
cvar.wait(started). This method takes the MutexGuard, atomically unlocks the mutex, and puts the thread to sleep. When it returns, it has re-acquired the lock and returns a new guard.
39
cvar.notify_one(). Wakes up one thread blocked on this condition variable. If no threads are waiting, this is a no-op.