Rust Concurrency

Introduction to Concurrency in Rust

The worst aspect of concurrent programming is the need to deal with various threading models.

Rusts concurrency with its thread, message passing and shared state, in which it looks to Rust unique ownership system to avoid common concurrency bugs.

Using Threads for Concurrent Programming

Threads:

Threads allow multiple parts of a program to run concurrently. In Rust, you can spawn threads using the std::thread module.

Example:

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

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("Hello from the spawned thread! {}", i);
            thread::sleep(Duration::from_millis(500));
        }
    });

    for i in 1..5 {
        println!("Hello from the main thread! {}", i);
        thread::sleep(Duration::from_millis(1000));
    }

    handle.join().unwrap(); // Wait for the spawned thread to finish
}

                    
                  
  • Spawning a Thread: Use thread::spawn to create a new thread.
  • Joining a Thread: Use join to wait for a thread to finish. This ensures that the main thread waits for the spawned thread to complete.

Message Passing with Channels

Channels:

Channels provide a way to communicate between threads.

Rust's standard library offers channels (std::sync::mpsc), where mpsc stands for "multiple producer, single consumer."

Example:

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

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

    thread::spawn(move || {
        let val = String::from("Hello");
        tx.send(val).unwrap(); // Send a value
        // val is now moved and cannot be used here
    });

    let received = rx.recv().unwrap(); // Receive the value
    println!("Got: {}", received);
}
                    
                  
  • Creating a Channel: Use mpsc::channel to create a transmitter (tx) and a receiver (rx).
  • Sending Messages: Use tx.send to send messages through the channel.
  • Receiving Messages: Use rx.recv to receive messages.

Multiple Producers:

You apply cloning the transmitter in order to facilitate multiple threads to deliver messages to the same receiver.

Example:

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

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

    for i in 0..3 {
        let tx_clone = tx.clone();
        thread::spawn(move || {
            let val = format!("Message from thread {}", i);
            tx_clone.send(val).unwrap();
            thread::sleep(Duration::from_millis(500));
        });
    }

    for received in rx {
        println!("Got: {}", received);
    }
}
                    
                  

  • Cloning the Transmitter: Use tx.clone() to create multiple producers.
  • Iterating Over Received Messages: The use of receiver as an iterator allows it to receive messages in a continuous manner up to the moment that all senders are ceased.

Summary

  • Concurrency: Rust's ownership model ensures safe concurrent programming.
  • Threads: Create and manage threads with std::thread.
  • Message Passing: Use channels (std::sync::mpsc) for safe communication between threads.

Rust's concurrency model which is built with data-safety and deadlock-avoidance can put you in a position to develop reliable and speedy concurrent programs.