Concurrency

Concurrent programming, where different parts of a program execute independently, and parallel programming, where different parts of a program execute at the same time, are becoming increasingly important as more computers take advantage of their multiple processors. When multiple transactions interact with the same data simultaneously, potential inconsistencies and data integrity issues are possible. Rust provides full support of "Fearless Concurrency" that ensures memory saftey executing multiple tasks. Here are different approaches in rust to write concurrent code:

  • Mutexes and Locks: Use the std::sync module to create mutexes and locks, which allow exclusive access to shared resources. Example: Mutex::new(0) and lock().unwrap() to ensure only one thread modifies the shared data at a time.
  • Channels: Implement message passing between threads using channels, such as tokio::sync::mpsc::channel. This approach allows threads to communicate without blocking, and can be used with Tokio’s async runtime.
  • Atomic Reference Counting: Use Arc (Atomically Reference Counted) smart pointers to share ownership of data between threads, ensuring the data is not dropped until all threads are finished using it.
  • Async/Await: Leverage Tokio’s async/await syntax to write concurrent code that’s easier to read and maintain. This approach is particularly useful for I/O-bound tasks.
  • Rayon: Utilize the Rayon crate, which provides a parallelism library for Rust. It offers a high-level API for parallelizing tasks and can be used with Tokio’s async runtime.
  • Tokio: Use Tokio’s async runtime to write concurrent code that’s designed for I/O-bound tasks. Tokio provides a set of building blocks, including async channels, timers, and file I/O.
  • Condvars: Employ condition variables (Condvars) to synchronize threads, allowing them to wait for specific conditions to be met before proceeding.
  • Manual Low-Level Async: Implement low-level async programming using mio, a Rust library for building asynchronous I/O applications. This approach requires a deeper understanding of Rust’s concurrency model and is typically used for performance-critical code.
  • Parallelism: Use Rust’s built-in parallelism features, such as std::thread::scope and std::parallel::Iterator, to parallelize tasks and take advantage of multi-core processors.

Threads

Rust threads work similarly to threads in other languages. Threads are all daemon threads, the main thread does not wait for them, hence the program can end before spawn complete the process. Thread panics are independent of each other. Panics can carry a payload, which can be unpacked with downcast_ref.

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

fn main() {
    thread::spawn(|| {
        for i in 1..10 {
            println!("Count in thread: {i}!");
            thread::sleep(Duration::from_millis(5));
        }
    });

    for i in 1..5 {
        println!("Main thread: {i}");
        thread::sleep(Duration::from_millis(5));
    }
}

Async and await

The async and await keywords in Rust enable writing asynchronous functions and handling asynchronous operations in a way that resembles synchronous code, improving readability and maintainability. These are usually used to asynchronously handle file operations, or I/O operations without blocking the thread.

use tokio::fs::File;
use tokio::io::{self, AsyncReadExt};

async fn read_file_content(path: &str) -> Result<String, io::Error> {
    let mut file = File::open(path).await?; // Asynchronously open the file
    let mut content = String::new();
    file.read_to_string(&mut content).await?; //Asynchronously read the file content
    Ok(content) // Return the content if no errors
}
// Main function to run the async function
#[tokio::main]
async fn main() {
    match read_file_content("example.txt").await {
        Ok(content) => println!("File content: {}", content),
        Err(e) => eprintln!("Error reading file: {}", e),
    }
}