# Threads, Arc, Mutex and Channels — Rust

Source: https://www.skillbyai.com/en/rust/p-threads

> Share safely or pass messages.

## Three patterns

`thread::spawn` with a `move` closure runs code on a new OS thread. To **share** mutable state, wrap it in `Arc<Mutex<T>>`: `Arc` gives shared ownership across threads, `Mutex` ensures exclusive access, and the lock guard unlocks automatically when dropped. To **pass messages**, use channels (`mpsc::channel`): senders can be cloned, and the receiver iteration ends when all senders are dropped. **Scoped threads** (`thread::scope`) can borrow local data because they are guaranteed to finish before the scope ends.

## Threads the compiler checks

Rust's type system rules out data races; threads, Arc, Mutex and channels share work safely.

![Three ideas: threads and sharing, Send and Sync, async.](assets/figures/rust/section-7-map.svg) — Figure 7.1 — Threads, Send/Sync and async.

## A shared counter, a channel and scoped threads, run

I ran this with Rust 1.99.0 (cargo run, edition 2024, standard library only). Eight threads add 1,000 each to a mutex-protected counter for exactly 8,000; three threads send squares over a channel; a scoped thread borrows data that remains usable afterwards.

```rust
use std::sync::{mpsc, Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));      // shared ownership + exclusive access
    let mut handles = vec![];
    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());

    let (tx, rx) = mpsc::channel();
    for id in 0..3 {
        let tx = tx.clone();
        thread::spawn(move || tx.send(id * id).unwrap());
    }
    drop(tx);                                    // close the original sender
    let mut results: Vec<i32> = rx.iter().collect();
    results.sort();
    println!("squares from threads: {results:?}");

    let data = vec![1, 2, 3];
    let sum = thread::scope(|s| {                // scoped threads may borrow local data
        let h = s.spawn(|| data.iter().sum::<i32>());
        h.join().unwrap()
    });
    println!("scoped sum {sum}, data still usable: {data:?}");
}
```

Output:

```
counter = 8000
squares from threads: [0, 1, 4]
scoped sum 6, data still usable: [1, 2, 3]
```

## Keep lock scopes short

Lock, do the minimal work, and let the guard drop; long-held locks serialise your program.

**Quiz:** Why wrap a shared counter in Arc<Mutex<i32>>?

- [ ] To make it immutable
- [x] Arc shares ownership across threads and Mutex ensures exclusive access
- [ ] To avoid using threads
- [ ] Because i32 cannot be copied

*Answer:* Arc shares ownership across threads and Mutex ensures exclusive access. Shared ownership plus synchronisation.
