16.1. Using Threads to Run Code Simultaneously

use std::thread;
use std::time::Duration;

fn main() {
    let handle = thread::spawn(|| { // keep a handle of thread
        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(); // wait for spawned thread to finish
}

Using move Closures with Threads

Since Rust will infer the closure’s trait, one needs to use move to make closures acquire environments by taking ownership. In below example, if there is no move , the closure will implement Fn because Rust thinks (by analyzing closure body) the closure does not need ownership. As a result, Rust thinks the borrow of v in closure might outlive the v outside (Rust does not know how long the closure will live.)

use std::thread;

fn main() {
    let v = vec![1, 2, 3];

    let handle = thread::spawn(move || {
        println!("Here's a vector: {:?}", v);
    });

    handle.join().unwrap();
}

16.2. Using Message Passing to Transfer Data Between Threads

We create a new channel using the mpsc::channel function; mpsc stands for multiple producer, single consumer.

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || { // this thread needs to own the tx to send messages
        let val = String::from("hi");
        tx.send(val).unwrap();
    });

    let received = rx.recv().unwrap(); // recv blocks main thread; use try_recv for non-blocking purpose.
    println!("Got: {}", received);
}

A channel is said to be closed if either the transmitter or receiver half is dropped.

Channels and Ownership Transference

Below code won’t compile:

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let val = String::from("hi");
        tx.send(val).unwrap();
        println!("val is {}", val); // borrow after moved
    });

    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}

16.3. Shared-State Concurrency

In a way, channels in any programming language are similar to single ownership, because once you transfer a value down a channel, you should no longer use that value. Shared memory concurrency (e.g. mutexes) is like multiple ownership: multiple threads can access the same memory location at the same time.

The API of Mutex<T>

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(5);

    {
        let mut num = m.lock().unwrap(); // we must call lock to get the inner value
        *num = 6;
    }

    println!("m = {:?}", m);
}

Sharing a Mutex<T> Between Multiple Threads

Below code won’t compile:

use std::sync::Mutex;
use std::thread;

fn main() {
    let counter = Mutex::new(0);
    let mut handles = vec![];

    for _ in 0..10 {
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap(); // counter is moved 

            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

Multiple Ownership with Multiple Threads