GGistDev

Concurrency in Rust

Rust offers fearless concurrency with compile‑time guarantees. Use threads for parallel execution and message passing or shared state with synchronization.

Threads

use std::thread;
let handle = thread::spawn(|| {
    2 + 2
});
let result = handle.join().unwrap();

Use move to transfer ownership into the thread.

Sharing data: Arc and Mutex

Arc<T> provides atomic reference counting for shared ownership across threads. Wrap mutable shared data with Mutex<T>.

use std::{sync::{Arc, Mutex}, thread};
let data = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..4 {
    let shared = Arc::clone(&data);
    handles.push(thread::spawn(move || {
        let mut guard = shared.lock().unwrap();
        *guard += 1;
    }));
}
for h in handles { h.join().unwrap(); }

Avoid long‑held locks; keep critical sections small.

Channels (message passing)

use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
    tx.send("hello").unwrap();
});
println!("{}", rx.recv().unwrap());

Cloning the sender yields multi‑producer channels.

Send and Sync

  • Send: safe to move to another thread
  • Sync: &T can be shared across threads Most primitives implement these auto traits when their fields do.

Atomics

For lock‑free counters and flags, use std::sync::atomic::{AtomicUsize, Ordering}. Prefer channels/mutexes unless you need atomics.

Patterns

  • Prefer message passing to minimize shared mutable state
  • Use Arc<Mutex<T>> only when necessary; avoid nested locks and deadlocks
  • Consider crates like crossbeam for advanced concurrency

Summary

  • Threads execute work in parallel; use move for ownership
  • Choose channels for communication; Arc<Mutex<T>> for shared mutable state
  • Understand Send/Sync and prefer simple designs