C.W.K.
Stream
Lesson 04 of 05 · published

Shared State with Arc<Mutex>

~12 min · concurrency, arc, mutex, shared-state

Level 0Rust Curious
0 XP0/80 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete

The other coordination style is shared state: several threads reading and writing one piece of data. The danger is obvious — two threads writing at once corrupts it. Rust's safe shared-mutable type is Arc<Mutex<T>>.

Mutex: one thread at a time

A Mutex<T> (mutual exclusion) guards its inner value: a thread calls .lock() to get exclusive access, and no other thread can lock it until the first releases. The lock returns a guard; you mutate through the guard, and when the guard drops (end of scope — RAII again) the lock releases automatically. You literally cannot reach the data without holding the lock.

Arc to share the Mutex

A Mutex alone has one owner. To let multiple threads reach it, wrap it in an Arc: Arc<Mutex<T>>. Each thread gets a cloned Arc handle to the same Mutex. The Arc shares ownership across threads; the Mutex serializes access. This is the exact multi-threaded parallel of Rc<RefCell<T>> from the Smart Pointers track.

Arc shares ownership; Mutex serializes access. Together, Arc<Mutex<T>> gives many threads safe shared-mutable access: clone the Arc to share, lock the Mutex to mutate one-at-a-time. The lock guard's Drop releases it automatically — you can't forget to unlock.

The compiler still has your back

You can't accidentally read the data without locking — the value lives inside the Mutex, only reachable through .lock(). And you can't share a bare Mutex across threads without the Arc; the compiler requires it. Even the 'shared mutable state' path, the most dangerous in other languages, is race-free by construction here.

Code

Ten threads safely incrementing a shared counter·rust
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);
        handles.push(thread::spawn(move || {
            let mut n = counter.lock().unwrap(); // exclusive access
            *n += 1;
        })); // the lock guard drops here -> lock released
    }
    for h in handles {
        h.join().unwrap();
    }

    println!("final: {}", *counter.lock().unwrap()); // 10, always
}

External links

Exercise

Use Arc<Mutex<Vec<i32>>> shared across 5 threads; each thread pushes its id into the vector. After joining all, print the sorted vector and confirm all 5 ids are present. Why is the final result deterministic in content even though the push order is not?
Hint
The Mutex guarantees each push is exclusive, so no push is lost — all 5 ids land. The order they land in is nondeterministic (scheduler-dependent), which is why you sort before printing to get a deterministic display.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.