16 / 20 · Day 6
Day 6 · Concept 16

Threads & sync

Real OS threads via std::thread::spawn. Shared mutable state via Arc<Mutex<T>>. The Send + Sync marker traits enforce safe transfer at compile time. Data races are a compile error, not a runtime surprise.


1 · Spawn a thread

rust src/main.rs
use std::thread;

fn main() {
    let h = thread::spawn(|| {
        for i in 0..3 {
            println!("worker {}", i);
        }
    });

    for i in 0..3 {
        println!("main   {}", i);
    }

    h.join().unwrap();   // wait
}

spawn takes a closure; join blocks until it finishes. Interleaving order is OS-scheduler dependent.

2 · Sharing data — Arc + Mutex

rust src/main.rs · counter shared between threads
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..5 {
        let c = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            let mut n = c.lock().unwrap();
            *n += 1;
        }));
    }

    for h in handles { h.join().unwrap(); }

    println!("counter = {}", *counter.lock().unwrap());
}
Arc vs Rc. Rc<T> is single-threaded reference counting. Arc<T> is atomic — safe across threads. The compiler will reject Rc with a "cannot be sent between threads safely" error.

3 · Channels — message passing

rust src/main.rs
use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    for i in 0..3 {
        let tx = tx.clone();
        thread::spawn(move || tx.send(i * 10).unwrap());
    }
    drop(tx);  // close after all clones drop

    for got in rx {
        println!("got {}", got);
    }
}

mpsc = multi-producer, single-consumer. The tx can be cloned and sent to many threads; the rx stays in one place.

4 · Send and Sync — the marker traits

TraitMeans
SendCan be transferred to another thread (moved across).
Sync&T can be shared between threads (read-only access from many).

Both are auto-derived for almost every type. The exceptions: Rc, RefCell, raw pointers. The compiler rejects sending these across threads.

5 · Common mistakes

  • Using Rc across threads. Compiler error. Use Arc.
  • Holding a Mutex guard across an await. Deadlock risk. Drop the guard before awaiting; or use tokio::sync::Mutex in async.
  • Deadlocks from nested locks. Always acquire in the same order. Or use a single bigger lock.
  • Forgetting to drop the sender before the receiver loop — the loop hangs forever.

6 · When it clicks

  • "Share by communicating" — channels first; Arc<Mutex> when actually shared.
  • The Send/Sync error messages stop being mysterious — they're telling you exactly what's not thread-safe.
  • You distinguish threads (CPU-heavy) from async tasks (I/O-heavy) without thinking.
Found this useful?