Threads

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.

Creation

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.

Waiting for All Threads to Finish Using join Handles

The 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();
}

Using move Closures with Threads

move 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();

Message Passing - Data Transfer Between Threads

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.