Rust Mutex and Arc
Channels pass data between threads. Some programs need several threads to read and update one shared value, such as a visitor counter on a website. Rust allows shared data through two tools used together: Mutex guards access, and Arc shares ownership. Picture a single restroom key at a cafe. Only the person holding the key may enter, and everyone else waits.
Mutex: One Thread at a Time
The name Mutex comes from mutual exclusion. A mutex wraps a value and hands out access to one thread at a time. Call lock to obtain the key.
use std::sync::Mutex;
fn main() {
let counter = Mutex::new(0);
{
let mut value = counter.lock().unwrap();
*value += 1;
} // key returned here
println!("Counter: {:?}", counter);
}
Thread wants data
|
v
lock() --- key free? --yes--> get access
| |
no v
| use the value
v |
wait for key <---- key released at end of scope
Automatic Unlocking
The lock method returns a guard object. The guard releases the key when it leaves scope. No manual unlock call exists, which prevents forgotten unlocks.
Why unwrap Appears
A thread that panics while holding the key poisons the mutex. The next lock call returns an error to warn you that the data may be half-updated.
The Sharing Problem
Ten threads cannot each own one mutex. Moving the mutex into the first thread leaves nothing for the rest, so the compiler rejects the code. A reference-counting pointer is needed.
Arc: Shared Ownership Across Threads
The type Arc stands for atomic reference counted. Each clone adds one to a counter, and the data disappears when the last clone is dropped. The atomic part makes counting safe across threads.
The simpler Rc type skips that safety for speed, and the compiler forbids sending Rc to another thread.
Arc counter = 3
+-----------+-----------+
| | |
Thread 1 Thread 2 Thread 3
\ | /
v v v
+--------------------+
| Mutex<i32> |
| value: 0 |
+--------------------+
Combining Arc and Mutex
The pair Arc<Mutex<T>> is the standard recipe for shared mutable data. The program below starts ten threads, and each adds one to a shared counter.
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().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
Result: 10
The result is always 10, because the mutex turns ten simultaneous updates into ten orderly ones.
Reading Many, Writing One with RwLock
A Mutex blocks readers as well as writers. The RwLock type allows many readers at once, or a single writer.
use std::sync::{Arc, RwLock};
let data = Arc::new(RwLock::new(vec![1, 2, 3]));
{
let reader = data.read().unwrap();
println!("{:?}", *reader);
}
{
let mut writer = data.write().unwrap();
writer.push(4);
}Simple Counters with Atomics
For plain numbers, atomic types avoid a lock entirely and run faster.
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
let hits = Arc::new(AtomicUsize::new(0));
hits.fetch_add(1, Ordering::SeqCst);
println!("{}", hits.load(Ordering::SeqCst));Deadlock Warning
A deadlock happens when two threads each hold one key and wait for the other key. Neither can continue.
Thread A holds Lock 1, wants Lock 2
Thread B holds Lock 2, wants Lock 1
--> both wait forever
Avoid deadlocks with these habits.
- Lock resources in the same order everywhere.
- Hold a lock for the shortest time possible.
- Avoid calling unknown code while holding a lock.
- Prefer channels when a design allows it.
Choosing a Tool
| Need | Tool |
|---|---|
| Share one value between threads, with updates | Arc<Mutex<T>> |
| Many readers, rare writers | Arc<RwLock<T>> |
| Simple counters and flags | Atomic types |
| Pass work between threads | Channels |
Key Points
Mutexlets one thread at a time reach the data.Arcshares ownership across threads safely.- The guard from
lockunlocks automatically at scope end. - Use
RwLockor atomics when they fit the job better. - Avoid deadlocks by locking in a consistent order.
