Prerequisites

To follow this tutorial, ensure that you have the following installed:

  • Rust (latest stable version)
  • Cargo (Rust's package manager)

You can install Rust and Cargo by following the instructions on the official Rust installation page.

Creating a New Rust Project

First, create a new Rust project using Cargo. Open your terminal and run:

cargo new multithreaded_sum
cd multithreaded_sum

This command creates a new directory named multithreaded_sum with a default Rust project structure.

Understanding the Application Structure

In this example, we will create a simple application that splits a large array of numbers into chunks, processes each chunk in a separate thread, and then combines the results.

The main components of our application will include:

  • Generating a large array of random numbers.
  • Splitting the array into smaller chunks.
  • Spawning threads to compute the sum of each chunk.
  • Combining the results from all threads.

Implementing the Multithreaded Sum

Open the src/main.rs file and replace its content with the following code:

use std::thread;
use std::sync::{Arc, Mutex};
use rand::Rng;

const ARRAY_SIZE: usize = 1_000_000;
const THREAD_COUNT: usize = 8;

fn main() {
    // Generate a large array of random numbers
    let numbers: Vec<i32> = (0..ARRAY_SIZE).map(|_| rand::thread_rng().gen_range(0..100)).collect();

    // Split the array into chunks
    let chunk_size = ARRAY_SIZE / THREAD_COUNT;
    let mut handles = vec![];
    let total_sum = Arc::new(Mutex::new(0));

    for i in 0..THREAD_COUNT {
        let start_index = i * chunk_size;
        let end_index = if i == THREAD_COUNT - 1 { ARRAY_SIZE } else { start_index + chunk_size };
        let numbers_chunk = &numbers[start_index..end_index];
        let total_sum_clone = Arc::clone(&total_sum);

        // Spawn a new thread
        let handle = thread::spawn(move || {
            let sum: i32 = numbers_chunk.iter().sum();
            let mut total = total_sum_clone.lock().unwrap();
            *total += sum;
        });

        handles.push(handle);
    }

    // Wait for all threads to finish
    for handle in handles {
        handle.join().unwrap();
    }

    // Output the total sum
    println!("Total sum: {}", *total_sum.lock().unwrap());
}

Code Explanation

  1. Dependencies: We use the rand crate for generating random numbers. Add the following line to your Cargo.toml under [dependencies]:
   rand = "0.8"
  1. Array Generation: We create a large array of random integers between 0 and 99 using the rand crate.
  1. Chunking: The array is split into smaller chunks based on the number of threads specified.
  1. Thread Creation: For each chunk, we spawn a new thread that computes the sum of its assigned numbers. We use Arc (Atomic Reference Counted) and Mutex (Mutual Exclusion) to safely share the total sum across threads.
  1. Joining Threads: After spawning all threads, we wait for each to finish using join().
  1. Output: Finally, we print the total sum calculated by all threads.

Running the Application

To run your multithreaded application, execute the following command in your terminal:

cargo run

You should see an output similar to:

Total sum: 49999950

Performance Considerations

When working with multithreading in Rust, consider the following best practices:

Best PracticeDescription
Minimize Shared StateReduce the use of shared mutable state to avoid contention and deadlocks.
Use Arc and Mutex CarefullyUse Arc for shared ownership and Mutex for safe mutable access.
Benchmark Your CodeUse tools like cargo bench to measure performance and optimize.
Avoid Excessive Thread CreationLimit the number of threads to the number of CPU cores for efficiency.

Conclusion

In this tutorial, you've learned how to build a simple multithreaded application in Rust that calculates the sum of a large array of numbers. By leveraging Rust's concurrency features, you can create efficient applications that utilize multiple CPU cores effectively.

Learn more with useful resources