How to write concurrent Rust with threads and channels
Write concurrent Rust: spawn threads, move data in, pass messages over mpsc channels, share state with Arc and Mutex, and rely on Send and Sync for compile-time data-race safety.
Fearless concurrency in Rust
Rust calls its concurrency story "fearless" because the ownership and type system catch data races at compile time. If code that shares data unsafely between threads compiles, it is, by design, free of data races. You coordinate threads with channels for message passing or with Arc and Mutex for shared state.
Prerequisites
- Rust and Cargo installed
- Familiarity with ownership and borrowing
Steps
1. Spawn a thread
use std::thread;
let handle = thread::spawn(|| {
println!("running in a thread");
});
handle.join().unwrap();
2. Move data into a thread
A thread may outlive the spawning scope, so it must own the data it uses. The move keyword transfers ownership.
let data = vec![1, 2, 3];
thread::spawn(move || {
println!("{:?}", data);
});
3. Send messages with channels
The standard library provides multi-producer, single-consumer channels.
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
thread::spawn(move || tx.send(42).unwrap());
let value = rx.recv().unwrap(); // 42
4. Share state with Arc and Mutex
To share mutable state across threads, wrap it in a Mutex for safe access and an Arc for shared ownership.
use std::sync::{Arc, Mutex};
let counter = Arc::new(Mutex::new(0));
let c = Arc::clone(&counter);
thread::spawn(move || {
*c.lock().unwrap() += 1;
});
5. Understand Send and Sync
The Send and Sync marker traits tell the compiler which types can cross thread boundaries. The compiler enforces them automatically, which is what makes the safety guarantees hold.
6. Join threads and handle results
Call join on each handle to wait for completion and to propagate panics or return values.
Verification
Write a program that spawns several threads, each incrementing a shared Arc<Mutex<i32>>, join them all, and print the final count. Confirm the total is correct. Then try sharing a non-thread-safe type and confirm the compiler refuses to build it.
Next Steps
Explore scoped threads, RwLock for read-heavy workloads, atomic types for lock-free counters, and async runtimes such as Tokio for I/O-bound concurrency.
Prerequisites
- Basic Rust and ownership
- Rust and Cargo installed
Steps
- 1Spawn a thread
- 2Move data into a thread
- 3Send messages with channels
- 4Share state with Arc and Mutex
- 5Understand Send and Sync
- 6Join threads and handle results