Profile Photo

Jamie Skipworth


Technology Generalist | Software & Data


Rusty Threads

Fearless Concurrency

Another thing Rust boasts is “fearless concurrency”, another concept that’s hard to implement correctly. I’ll only very briefly touch on this.

Previously I had a play around with concurrency in Go and really liked it. Go uses “green” threads provided by the language that are then multiplexed on to “real” OS threads. Rust uses OS threads, so one Rust thread runs as one OS thread.

In Rust threads aren’t as easily created as they are in Go, but both languages have the concept of “channels” though, making inter-thread communications easier. Here’s a quick example:

use std::thread;
use std::sync::mpsc;
use std::time::Duration;

fn main() {

    // Create a channel - tx for transmit, rx for receive.
    // mpsc stands for "multiple producer, single consumer"
    let ( tx, rx ) = mpsc::channel();

    // Threads execute closures.
    // Create a thread that transmits a string down the channel.
    // Using "move" transfers ownership of any referenced variables
    // into the thread.
    thread::spawn( move || {
        let foo = String::from( "Hello, world!" );
        tx.send( foo ).unwrap();
        }
    );

    // Receive a value from the channel
    let rec = rx.recv().unwrap();
    println!( "Received: {}", rec );
}

There’s a fair bit going on here, so I’d suggest reading the documentation here. I’m doing 2 things:

  1. I’m first creating a channel so that my thread is able to communicate with the main program.
  2. I’m spawning a thread that executes some code that will send data into the channel.

The channel being created is a Multiple Producer, Single Consumer channel (mpsc) - many threads can write to it, but only one can read.

Rust threads run closures (anonymous functions - read the docs). The pipes || enclose parameters similar to how closures are defined in Ruby, so if I wanted to pass-in values I could do this: | my_string, my_int |.

Alternatively, you can use the move statement which tells Rust that you want to use any in-scope variables referenced by the thread. Remember, with Rust this would also transfer ownership, so don’t get tripped-up by the borrow-checker.

Output:

$ ./thread
Received: Hello, world!

Here’s similar code that throws a load of fruit down a channel:

use std::thread;
use std::sync::mpsc;
use std::time::Duration;

fn main() {

    // Create a channel - tx for transmit, rx for receive.
    // mpsc stands for "multiple producer, single consumer"
    let ( tx, rx ) = mpsc::channel();

    // Threads execute closures.
    // Create a thread that transmits strings down the channel.
    // Using "move" transfers ownership of any referenced variables
    // into the thread.
    thread::spawn( move || {
            let str_vector = vec![
                String::from( "apple" ),
                String::from( "banana" ),
                String::from( "orange" ),
                String::from( "kiwi" )
            ];
            
            for value in str_vector {
                tx.send( value ).unwrap();    
                thread::sleep( Duration::from_secs( 1 ) );
            }
        }
    );

    // Receive values from the channel
    for received in rx {
        println!( "Received: {}", received );
    }
}

Output:

$ ./thread2
Received: apple
Received: banana
Received: orange
Received: kiwi

So that’s a very basic overview of how to start threads in Rust, and how to create a communications channel.

Next up I’ll modify my Rust word counter to use a thread per file, which should dramatically increase performance.