Sharing a counter between threads with Arc<Mutex<T>>

Read the explanation
What if

Two threads add 1 to a shared counter. The second waits in lock() until the first drops its guard.

Predict first

Instead of Arc<Mutex<i32>>, two threads do count += 1 on a plain let mut count = 0. What happens?

01234567main: spawn thread 1spawnmain: spawn thread 2spawnjoin()print 2lock()*n += 1 → 1waits in lock()*n += 1 → 2

Tap any block to see what that thread is doing.

  • Running
  • Waiting / parked
  • Holding lock
  • Blocked
Step 1/5

Arc::clone gives each thread its own handle to the same Mutex.

1fn main() {2    let count = Arc::new(Mutex::new(0));3    let mut handles = vec![];4    for _ in 0..2 {5        let count = Arc::clone(&count); // one more owner of the same counter6        handles.push(thread::spawn(move || {7            let mut n = count.lock().unwrap(); // waits while another thread holds the lock8            *n += 1;9        })); // n is dropped here: the lock is released10    }11    for h in handles {12        h.join().unwrap();13    }14    println!("{}", *count.lock().unwrap()); // always 215}

Explanation

What just happened

Arc::clone gave each thread its own handle to the same Mutex. Thread 1 called lock() and got a guard, n. While n was alive, thread 2’s lock() waited. When thread 1’s closure ended, n was dropped and the lock released. Thread 2 then saw 1 and wrote 2.

No unlock() call anywhere: the guard going out of scope is the unlock.

Try the what-if switch

Why Rust makes you write this

Try sharing a plain let mut count = 0 with thread::spawn. It doesn’t compile: the thread might outlive count. With move, each thread changes its own copy and main still sees 0. The data race from other languages can’t be written here.

Watch out

Keep guards short-lived. If a thread panics while holding one, the mutex is poisoned.

Common questions

Why do I need Arc<Mutex<T>> to share data between threads?

Arc lets several threads own the same value, and Mutex lets one thread at a time change it. Without them, a closure that borrows a local counter doesn't compile (error E0373), and Rc doesn't either: it can't be sent between threads (E0277).

When is a Mutex unlocked in Rust?

When the guard that lock() returned is dropped, usually at the end of its scope. Call drop(guard) to release it earlier.

What is a poisoned mutex?

A thread panicked while holding the guard. After that, every lock() returns an error, so lock().unwrap() panics too.

Verified: this timeline matches a real program, checked in CI on Rust 1.99 with Tokio 1.54. How we verify. Unclear or wrong? Tell us