Shared State: Arc & Mutex
Prerequisites: Threads & message passing
Channels move data between threads. Sometimes several threads need to read and update the same data instead. Rust's tools for that are:
Arc<T>— "atomically reference counted". The thread-safe version ofRc<T>: cloning it gives another owner of the same value, and the count is updated safely from any thread.Mutex<T>— "mutual exclusion". Only one thread at a time can access the value inside..lock()waits until the lock is free and returns a guard; the guard derefs to the data and releases the lock automatically when it is dropped.RwLock<T>— a read-write lock: any number of readers at once (.read()), or a single writer (.write()). Useful when reads far outnumber writes.
Together, Arc<Mutex<T>> is the standard way to share mutable state between
threads: Arc provides shared ownership, Mutex provides safe mutation.
lock() returns a Result. It is an Err only if another thread panicked
while holding the lock (the mutex is then "poisoned"). Calling .unwrap() on
it is the common choice, since that situation usually means a bug anyway.
Syntax
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let c = Arc::clone(&counter); // another owner for the thread
thread::spawn(move || {
let mut n = c.lock().unwrap(); // wait for the lock, get a guard
*n += 1;
}) // guard dropped here: lock released
.join()
.unwrap();
println!("{}", *counter.lock().unwrap());
}
Example
use std::collections::HashMap;
use std::sync::{Arc, Mutex, RwLock};
use std::thread;
fn main() {
// A shared counter updated by 8 threads.
let counter = Arc::new(Mutex::new(0));
let mut handles = Vec::new();
for _ in 0..8 {
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
for _ in 0..1000 {
*counter.lock().unwrap() += 1;
}
}));
}
for h in handles {
h.join().unwrap();
}
println!("counter = {}", *counter.lock().unwrap());
// A shared map: each thread records its own result.
let results = Arc::new(Mutex::new(HashMap::new()));
let handles: Vec<_> = ["apple", "fig", "banana"]
.into_iter()
.map(|word| {
let results = Arc::clone(&results);
thread::spawn(move || {
let length = word.len(); // do the work *before* taking the lock
results.lock().unwrap().insert(word, length);
})
})
.collect();
for h in handles {
h.join().unwrap();
}
let map = results.lock().unwrap();
let mut entries: Vec<_> = map.iter().collect();
entries.sort();
println!("lengths: {entries:?}");
// RwLock: many readers, occasional writer.
let config = Arc::new(RwLock::new(String::from("v1")));
{
let reader1 = config.read().unwrap();
let reader2 = config.read().unwrap(); // two readers at once is fine
println!("readers see {reader1} and {reader2}");
}
config.write().unwrap().push_str("-patched");
println!("after write: {}", config.read().unwrap());
}
Expected output
counter = 8000
lengths: [("apple", 5), ("banana", 6), ("fig", 3)]
readers see v1 and v1
after write: v1-patched
The counter always ends at exactly 8000: without the mutex, two threads could read the same old value and one update would be lost. Rust doesn't let you write that broken version at all.
Deadlocks
The compiler prevents data races, but not deadlocks — threads waiting on each other forever. Common causes:
- Locking the same
Mutextwice in one thread while the first guard is still alive.std::sync::Mutexis not re-entrant, so the secondlock()never returns (or panics — the behavior is unspecified). - Two threads taking two locks in opposite orders (thread 1 locks A then B, thread 2 locks B then A). Always acquire multiple locks in the same order.
Keep guards short-lived: copy out what you need, drop the guard (end the block
or call drop(guard)), then do slow work.
Common mistakes
- Sharing an
Rcwith a thread:error[E0277]: `Rc<Mutex<i32>>` cannot be sent between threads safely.Rc's counter isn't thread-safe; useArc. - Moving the
Arcitself into the first thread and then using it again:error[E0382]: use of moved value: `counter`. Clone theArcfor each thread before themoveclosure. - Using
Arc<T>alone and trying to modify through it:error[E0596]: cannot borrow data in an `Arc` as mutable.Arconly gives shared access; put aMutexorRwLockinside. - Holding a guard across slow work (file I/O, sleeping, a long computation). Every other thread waits the whole time, and the program may be no faster than a single thread.
- Reading under one lock and writing under another:
let n = *c.lock().unwrap(); *c.lock().unwrap() = n + 1;. Another thread can change the value between the two locks, so an update gets lost. Do the read and the write while holding a single guard.
Practice
Start with Arc::new(Mutex::new(Vec::new())). Spawn five threads with ids
0–4; each pushes id * 10 into the vector. After joining all threads, sort
the vector and print it along with its sum.
Show solution
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let values = Arc::new(Mutex::new(Vec::new()));
let handles: Vec<_> = (0..5)
.map(|id| {
let values = Arc::clone(&values);
thread::spawn(move || {
values.lock().unwrap().push(id * 10);
})
})
.collect();
for h in handles {
h.join().unwrap();
}
let mut v = values.lock().unwrap();
v.sort(); // push order depends on thread scheduling
let sum: i32 = v.iter().sum();
println!("{v:?} sum = {sum}");
}
[0, 10, 20, 30, 40] sum = 100