In most operating systems, an executed program’s code is run in a process, and the OS will manage multiple processes at once. Within a program, independent parts can run simultaneously on threads.
Rust standard library uses a 1:1 model of thread implementation, whereby a program uses one OS thread per one language thread.
To create a new thread, use thread::spawn
function and pass it a closure containing the code to be run in the new thread.
thread::spawn( || {
println!("Hello from thread!");
});
If main thread of a Rust program completes, all spawned threads are shut down.
join
HandlesThe return value of thread::spawn
can be saved, which has the type of JoinHandle
. It is an owned value that, when join
is called on it, will wait for its thread to finish.
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
handle.join().unwrap();
}
move
Closures with Threadsmove
can be used on spawn
to make the closure take ownership of the values it uses from the environment and transferring ownership of those values from one thread to another.
Since Rust can’t tell how long the spawned thread will run, it doesn’t know if a borrowed variable inside the capture will always be valid.
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
To accomplish message-sending concurrency, Rust’s standard library provides an implementation of channels.
A channel has 2 haves: a transmitter and a receiver. One part of the code calls methods on the transmitter with the data want to send, and another part checks the receiving end for arriving messages. A channel is said to be closed if either the transmitter or receiver half is dropped.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel(); // returns a tuple of (sender, receiver)
thread::spawn(move || { // transfer the ownership into the closure
let val = String::from("hi");
tx.send(val).unwrap();
});
let received = rx.recv().unwrap();
println!("Got: {}", received);
}
Receiver’s recv
will block the main thread’s execution and wait until a value is sent down the channel. Once it received, recv
will return it in a Result<T,E>
. When the transmitter closes, recv
will return an error to signal no more values will be coming.
On the other hand, receiver’s try_recv
would not block the main thread, but returns a result immediately: an Ok
value holding a message if one is available and an Err
value if there aren’t any.