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

NeedTool
Share one value between threads, with updatesArc<Mutex<T>>
Many readers, rare writersArc<RwLock<T>>
Simple counters and flagsAtomic types
Pass work between threadsChannels

Key Points

  • Mutex lets one thread at a time reach the data.
  • Arc shares ownership across threads safely.
  • The guard from lock unlocks automatically at scope end.
  • Use RwLock or atomics when they fit the job better.
  • Avoid deadlocks by locking in a consistent order.