Parallel Tasks

Add the rayon crate to your own project:

cargo add rayon

Other crates needed for the example code:

cargo add glob rand image

Mutate the elements of an array in parallel

rayon-badge cat-concurrency-badge

The example uses the rayon crate, which is a data parallelism library for Rust. rayon provides the par_iter_mut method for any parallel iterable data type. This is an iterator-like chain that potentially executes in parallel.

use rayon::prelude::*;

fn main() {
    let mut arr = [0, 7, 9, 11];
    arr.par_iter_mut().for_each(|p| *p -= 1);
    println!("{:?}", arr);
}

Test in parallel if any or all elements of a collection match a given predicate

rayon-badge cat-concurrency-badge

This example demonstrates using the rayon::any and rayon::all methods, which are parallelized counterparts to std::any and std::all. rayon::any checks in parallel whether any element of the iterator matches the predicate, and returns as soon as one is found. rayon::all checks in parallel whether all elements of the iterator match the predicate, and returns as soon as a non-matching element is found.

use rayon::prelude::*;

fn main() {
    let mut vec = vec![2, 4, 6, 8];

    assert!(!vec.par_iter().any(|n| (*n % 2) != 0));
    assert!(vec.par_iter().all(|n| (*n % 2) == 0));
    assert!(!vec.par_iter().any(|n| *n > 8 ));
    assert!(vec.par_iter().all(|n| *n <= 8 ));

    vec.push(9);

    assert!(vec.par_iter().any(|n| (*n % 2) != 0));
    assert!(!vec.par_iter().all(|n| (*n % 2) == 0));
    assert!(vec.par_iter().any(|n| *n > 8 ));
    assert!(!vec.par_iter().all(|n| *n <= 8 ));
}

Search items using given predicate in parallel

rayon-badge cat-concurrency-badge

For this example, add the rand crate to your own project:

cargo add rand

This example uses rayon::find_any and par_iter to search a vector in parallel for an element satisfying the predicate in the given closure.

If there are multiple elements satisfying the predicate defined in the closure argument of rayon::find_any, rayon returns the first one found, not necessarily the first one.

Also note that the argument to the closure is a reference to a reference (&&x). See the discussion on std::find for additional details.

#![allow(unused)]
fn main() {
{{#include examples/rayon-parallel-search.rs}
}

Sort a vector in parallel

rayon-badge rand-badge cat-concurrency-badge

This example will sort in parallel a vector of Strings.

Allocate a vector of empty Strings. par_iter_mut().for_each() populates random values in parallel. Although multiple options exist to sort an enumerable data type, par_sort_unstable is usually faster than stable sorting algorithms.

use std::str::from_utf8;
use rand::{Rng, thread_rng};
use rand::distributions::Alphanumeric;
use rayon::prelude::*; // par_iter_mut(), par_sort_unstable()

// Generate a random word, save it to the given string.
fn generate_random_word(word: &mut String) {
    let word_length = 5;
    let mut rng = thread_rng();

    // Generate 5 bytes in alphanumeric range.
    let mut alpha_bytes = vec![0; word_length];
    for i in 0 .. word_length {
        alpha_bytes[i] = rng.sample(&Alphanumeric);
    }
    *word = from_utf8(&alpha_bytes).unwrap().to_string();
}

// Generate random words in parallel, then sort them in parallel.
fn main() {
    let word_count = 10; // try increasing to 100000 or more
    let mut vec = vec![String::new(); word_count];

    // replace with vec.iter_mut() for single threaded
    vec.par_iter_mut().for_each(generate_random_word);

    // replace with vec.sort_unstable() for single-threaded
    vec.par_sort_unstable();

    for word in vec {
        println!("{word}");
    }
}

To truly exercise multiple threads of execution on multiple processor cores, change word_count to be 100,000 or larger. Compare the execution time and CPU load versus the single threaded versions of the same vector operations.

If the Rust Cookbook has been cloned, run:

cargo run --example rayon-parallel-sort

Map-reduce in parallel

rayon-badge cat-concurrency-badge

This example uses rayon::filter, rayon::map, and rayon::reduce to calculate the average age of Person objects whose age is over 30.

rayon::filter returns elements from a collection that satisfy the given predicate. rayon::map performs an operation on every element, creating a new iteration, and rayon::reduce performs an operation given the previous reduction and the current element. Also shows use of rayon::sum, which has the same result as the reduce operation in this example.

use rayon::prelude::*;

struct Person {
    age: u32,
}

fn main() {
    let v: Vec<Person> = vec![
        Person { age: 23 },
        Person { age: 19 },
        Person { age: 42 },
        Person { age: 17 },
        Person { age: 17 },
        Person { age: 31 },
        Person { age: 30 },
    ];

    let num_over_30 = v.par_iter().filter(|&x| x.age > 30).count() as f32;
    let sum_over_30 = v.par_iter()
        .map(|x| x.age)
        .filter(|&x| x > 30)
        .reduce(|| 0, |x, y| x + y);

    let alt_sum_30: u32 = v.par_iter()
        .map(|x| x.age)
        .filter(|&x| x > 30)
        .sum();

    let avg_over_30 = sum_over_30 as f32 / num_over_30;
    let alt_avg_over_30 = alt_sum_30 as f32/ num_over_30;

    assert!((avg_over_30 - alt_avg_over_30).abs() < std::f32::EPSILON);
    println!("The average age of people older than 30 is {}", avg_over_30);
}

Generate jpg thumbnails in parallel

rayon-badge glob-badge image-badge cat-concurrency-badge cat-filesystem-badge

Add the image crate to your own project:

cargo add image

This example generates thumbnails for all .jpg files in the current directory then saves them in a new folder called thumbnails.

glob::glob_with finds jpeg files in current directory. rayon resizes images in parallel using par_iter calling DynamicImage::resize.

use std::error::Error;
use std::path::Path;
use std::process::exit;
use std::fs::create_dir_all;

use glob::{glob_with, MatchOptions};
use image::imageops::FilterType;
use rayon::prelude::*;

fn main() -> Result<(), Box<dyn Error>> {
    let options: MatchOptions = Default::default();
    let files: Vec<_> = glob_with("*.jpg", options)?
        .filter_map(|x| x.ok())
        .collect();

    if files.len() == 0 {
        println!("No .jpg files found in current directory");
        exit(1);
    }

    let thumb_dir = "thumbnails";
    create_dir_all(thumb_dir)?;

    println!("Saving {} thumbnails into '{}'...", files.len(), thumb_dir);

    let image_failures: Vec<_> = files
        .par_iter()
        .map(|path| {
            make_thumbnail(path, thumb_dir, 100)
                .map_err(|e| println!("{e}: path: {}", path.display()))
        })
        .filter_map(|x| x.err())
        .collect();

    println!("{} thumbnails saved successfully", files.len() - image_failures.len());
    Ok(())
}

fn make_thumbnail<PA, PB>(original: PA, thumb_dir: PB, longest_edge: u32) -> Result<(), Box<dyn Error>>
where
    PA: AsRef<Path>,
    PB: AsRef<Path>,
{
    let img = image::open(original.as_ref())?;
    let file_path = thumb_dir.as_ref().join(original);

    Ok(img.resize(longest_edge, longest_edge, FilterType::Nearest)
        .save(file_path)?)
}