Concurrency
Arc and Mutex are legal to share. A closure can move. A lifetime can be 'static. This lesson puts those together: start a thread, then pass messages so you do not need a shared mutex for every job. Async, which waits on I/O without one operating-system thread per wait, is the following lesson.
Threads, Send, and Sync
thread::spawn takes a move closure and returns a handle. join waits for that thread and gives back its result. The closure must be Send, and everything it owns must live for 'static, because the thread might outlive the function that started it. A borrow of a local will not compile. thread::scope joins every spawned thread before the scope ends, so those threads may borrow the parent’s stack.
| Auto trait | What it allows | Example |
|---|---|---|
Send | Ownership may move to another thread | Most owned values. Rc is not Send. |
Sync | A shared reference may be used from many threads | Arc is both when the inner value is. An Arc of a Mutex is what you clone per worker when they share a counter. |
Send means ownership may move to another thread. Sync means a shared reference may be used from many threads. Rc is neither. Arc is both when the inner value is. An Arc of a Mutex, from the previous lesson, is what you clone per worker when they must share a counter.
A data-parallel iterator library such as rayon is a common way to split CPU work. This lesson stays on the standard library so the spawn and the join stay visible.
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
thread::sleep(Duration::from_millis(10));
7
});
let answer = handle.join().unwrap();
let data = vec![1, 2, 3];
thread::scope(|scope| {
scope.spawn(|| {
println!("{}", data.len());
});
});
println!("{answer}");
}Channels
mpsc is many producers and one consumer. Sending moves the value. The sender no longer has it, which is ordinary ownership applied to a queue.
| Call | What the sender does |
|---|---|
| unbounded channel | Grows if the consumer falls behind |
| sync_channel(n) | Blocks the sender when n messages are waiting |
| try_recv | Returns immediately if the queue is empty |
| recv_timeout | Waits only until the timeout |
tx.clone() makes another producer. Each closure moves its sender. After send, that thread does not still own the String.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
let tx2 = tx.clone();
thread::spawn(move || tx.send(String::from("one")).unwrap());
thread::spawn(move || tx2.send(String::from("two")).unwrap());
let first = rx.recv().unwrap();
let second = rx.recv().unwrap();
println!("{first} {second}");
}