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
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
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
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
| Trait | Means |
|---|---|
Send | Can 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::Mutexin 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?