rust fearless concurrency

Using threads to run code simultaneously

use std::thread;
use std::time::Duration;
 
// hi number 1 from the main thread!
// hi number 1 from the spawned thread!
// hi number 2 from the main thread!
// hi number 2 from the spawned thread!
// hi number 3 from the main thread!
// hi number 3 from the spawned thread!
// hi number 4 from the main thread!
// hi number 4 from the spawned thread!
// hi number 5 from the spawned thread!
fn main() {
    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));
    }
}
use std::thread;
use std::time::Duration;
 
// waiting for all threads to finish using join handles
// hi number 1 from the spawned thread!
// hi number 2 from the spawned thread!
// hi number 3 from the spawned thread!
// hi number 4 from the spawned thread!
// hi number 5 from the spawned thread!
// hi number 6 from the spawned thread!
// hi number 7 from the spawned thread!
// hi number 8 from the spawned thread!
// hi number 9 from the spawned thread!
// hi number 1 from the main thread!
// hi number 2 from the main thread!
// hi number 3 from the main thread!
// hi number 4 from the main thread!
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));
        }
    });
 
    handle.join().unwrap();
 
    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
}
use std::thread;
 
fn main() {
    let v = vec![1, 2, 3];
 
    // use move keyword to force the closure to take ownership of
    // the values
    let handle = thread::spawn(move || {
        println!("Here's a vector: {:?}", v);
    });
 
    handle.join().unwrap();
}

Using message passing to transfer data between threads

use std::sync::mpsc;
use std::thread;
 
fn main() {
    // mspsc::channel returns a tuple:
    // tx (transmitter) is the sending end
    // rx (receiver) second is the receiving end
    let (tx, rx) = mpsc::channel();
 
    thread::spawn(move || {
        let val = String::from("hi");
        tx.send(val).unwrap();
    });
 
    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}

The receiving end of a channel has two useful methods:

  • recv: block the main thread’s execution and wait until a value is sent down the channel
  • try_recv: doesn’t block but will instead return a Result<T, E> immediately: an Ok value holding a message if one is available and an Err value if there aren’t any messages this time
    • useful if the thread has other work to do while waiting

Channels and ownership transference

The send function takes ownership of its parameter, and when the value is moved, the receiver takes ownership of it. This stops us from accidentally using the value again after sending it.

Sending multiple values and seeing the receiver waiting

use std::sync::mpsc;
use std::thread;
use std::time::Duration;
 
fn main() {
    let (tx, rx) = mpsc::channel();
 
    thread::spawn(move || {
        let vals = vec![
            String::from("hi"),
            String::from("from"),
            String::from("the"),
            String::from("thread"),
        ];
 
        for val in vals {
            tx.send(val).unwrap();
            thread::sleep(Duration::from_secs(1));
        }
    });
 
    for received in rx {
        println!("Got: {}", received);
    }
}

Producing multiple producers by cloning the transmitter

use std::sync::mpsc;
use std::thread;
use std::time::Duration;
 
fn main() {
    // --snip--
 
    let (tx, rx) = mpsc::channel();
 
    let tx1 = tx.clone();
    thread::spawn(move || {
        let vals = vec![
            String::from("hi"),
            String::from("from"),
            String::from("the"),
            String::from("thread"),
        ];
 
        for val in vals {
            tx1.send(val).unwrap();
            thread::sleep(Duration::from_secs(1));
        }
    });
 
    thread::spawn(move || {
        let vals = vec![
            String::from("more"),
            String::from("messages"),
            String::from("for"),
            String::from("you"),
        ];
 
        for val in vals {
            tx.send(val).unwrap();
            thread::sleep(Duration::from_secs(1));
        }
    });
 
    for received in rx {
        println!("Got: {}", received);
    }
 
    // --snip--
}

The output should look something like this:

Got: hi
Got: more
Got: from
Got: messages
Got: for
Got: the
Got: thread
Got: you

Shared-state concurrency

Using mutexes to allow access to data from one thread at a time

Mutex allows only one thread to access some data at any given time.

use std::sync::Mutex;
 
fn main() {
    let m = Mutex::new(5);
 
    {
        let mut num = m.lock().unwrap();
        *num = 6;
    }
 
    // prints: m = Mutex { data: 6, poisoned: false, .. }
    println!("m = {:?}", m);
}

Mutex<T> is smart pointer.

Multiple ownership with multiple threads

Rc<T> is not safe to share across threads.

Atomic reference counting with Arc<T>

Arc<T> is a type like Rc<T> that is safe to use in concurrent situations.

use std::sync::{Arc, Mutex};
use std::thread;
 
fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];
 
    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
 
            *num += 1;
        });
        handles.push(handle);
    }
 
    for handle in handles {
        handle.join().unwrap();
    }
 
    // prints: Result: 10
    println!("Result: {}", *counter.lock().unwrap());
}

Extensible concurrency with the Sync and Send traits

Allowing transference of ownership between threads with Send

The Send marker trait indicates that ownership of values of the type implementing Send can be transferred between threads.

Allowing access from multiple threads with Sync

The Sync marker trait indicates that it is safe for the type implementing Sync to be referenced from multiple threads.

Manually implementing these traits involves implementing unsafe Rust code.