Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Mutex<T>

Goal of This Episode

Learn to let multiple Threads safely modify shared data with Mutex<T>.

Concept

What about Modifying Complex Shared Data?

Last episode’s atomics apply only to simple types like integers and booleans. What if several Threads should modify a Vec, a String, or any complex structure?

Mutex: Multithreaded Interior Mutability

Mutex<T> somewhat resembles RefCell — both provide interior mutability, modifying values without &mut. The difference:

  • RefCell: single-threaded, borrow-checking with an ordinary integer.
  • Mutex: multithreaded, guarding the data with an operating-system lock.

lock and MutexGuard

Acquire the lock with mutex.lock().expect("lock failed"). It returns a MutexGuard:

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(42);
    {
        let mut guard = m.lock().expect("lock failed");
        *guard += 1; // Modify the value through the guard
        println!("{}", *guard); // 43
    } // The guard is dropped; automatic unlock
}

MutexGuard implements Deref and DerefMut (from Chapter 5), making it a smart pointer too — usable directly as &T or &mut T.

Only one Thread can lock successfully at a time. Other Threads calling .lock() block (wait) until the lock-holding Thread drops its guard.

Arc + Mutex

In practice they usually pair up — Arc lets several Threads share the Mutex; the Mutex guards the data inside:

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

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().expect("lock failed");
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().expect("thread panicked");
    }

    println!("Result: {}", *counter.lock().expect("lock failed")); // 10
}

Don’t Let the MutexGuard Live Long

While the guard lives, the lock stays held, and every other Thread waits. So keep the guard’s lifespan short:

// Bad: the guard lives to scope's end; the lock is held too long
let mut guard = mutex.lock().expect("lock failed");
*guard += 1;
// ... lots of work that doesn't need the lock ...
// The guard only drops way down here

// Good: release when done
{
    let mut guard = mutex.lock().expect("lock failed");
    *guard += 1;
} // The guard drops immediately; the lock releases immediately
// ... other work ...

Mutex Turns Send into Sync

Episode 3 taught Send and Sync. Some types are Send but not Sync — Episode 4’s RefCell<T>, say: safely movable to another Thread (Send), but not accessible by several Threads at once through &RefCell<T> (not Sync).

Mutex solves this. Mutex<T> guarantees that only one Thread accesses T at a time — even with many Threads sharing one &Mutex<T>, only the lock-holder touches the inner T. So Mutex<T> requires only T: Send for Mutex<T> itself to be Sync.

Put differently: T not being Sync is fine — the Mutex’s locking already rules out simultaneous access. T needs Send because: Thread A takes the lock, works on T, releases; the next lock-taker might be Thread B. From T’s perspective, it was A’s exclusively, now it’s B’s exclusively — effectively T was “shipped” from A to B. Hence T must be Send.

Example Code

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

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for i in 0..5 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            // Shrinking the guard's scope
            {
                let mut num = counter.lock().expect("lock failed");
                *num += 1;
                println!("Thread {} set the counter to {}", i, *num);
            } // The guard drops right here

            // The lock is no longer held here
            println!("Thread {} is done", i);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().expect("thread panicked");
    }

    println!("Final result: {}", *counter.lock().expect("lock failed"));
}

Recap

  • Mutex<T> is multithreaded interior mutability, guarding data with a lock.
  • .lock().expect(...) returns a MutexGuard, usable directly as &mut T via DerefMut.
  • Only one Thread holds the lock at a time; the rest wait.
  • Dropping the guard unlocks automatically.
  • The common pairing: Arc<Mutex<T>>Arc for sharing, Mutex for safe modification.
  • Don’t let the MutexGuard live long; while locked, every other Thread waits.
  • Mutex<T> needs only T: Send to be Sync — its locking lets non-Sync types be safely shared among Threads.