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 aMutexGuard, usable directly as&mut TviaDerefMut.- Only one
Threadholds the lock at a time; the rest wait. - Dropping the guard unlocks automatically.
- The common pairing:
Arc<Mutex<T>>—Arcfor sharing,Mutexfor safe modification. - Don’t let the
MutexGuardlive long; while locked, every otherThreadwaits. Mutex<T>needs onlyT: Sendto beSync— its locking lets non-Synctypes be safely shared amongThreads.