0

For some background: I wanted to reach out and ask if there was a strategy in Rust for handling the divide and conquer portion of the mergesort algorithm in a multithreaded scenario using only the standard Rust library. I've seen some cool takes on similar problems completed using the crossbeam and rayon crates, but I'm trying to keep my use of external crates to a minimum while I'm learning the ins and outs of the language (my current experiences stem mainly from C, C++ and C#).

For the actual issue: While implementing the multithreaded version of the algorithm, I hit a wall with the compiler via lifetime differences between the function scope and the spawned threads. In the code block below, what would be the most appropriate course of action for abiding by the 'static lifetime required by the argument to thread::spawn?

I'm not dead set on the logic structure either, please do let me know if there's a more Rust-like way to achieve this like with iterators for instance.

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

/// implementation will not handle mutex lock failures or thread failures for brevity.
pub fn mergesort_mt(
    collection: &mut [u8],
    max_threads: usize,
    thread_counter: Arc<Mutex<usize>>,
) -> Vec<u8> {
    if collection.len() < 2 {
        return collection.to_vec();
    }
    let pivot = collection.len() / 2;
    let (mut left, mut right) = collection.split_at_mut(pivot);
    let mut mutex_value = thread_counter.lock().unwrap();
    let num_remaining = max_threads - *mutex_value;
    if num_remaining >= 2 {
        *mutex_value += 2;
        drop(mutex_value);
        let counter = Arc::clone(&thread_counter);
        let handle_left = thread::spawn(move || mergesort_mt(&mut left, max_threads, counter));
        let counter = Arc::clone(&thread_counter);
        let handle_right = thread::spawn(move || mergesort_mt(&mut right, max_threads, counter));

        let mut left = handle_left.join().unwrap();
        let mut right = handle_right.join().unwrap();

        return merge(&mut left[..], &mut right[..]);
    } else if num_remaining == 1 {
        *mutex_value += 1;
        drop(mutex_value);
        let counter = Arc::clone(&thread_counter);
        let handle_left = thread::spawn(move || mergesort_mt(&mut left[..], max_threads, counter));
        let mut left = handle_left.join().unwrap();
        let mut right = mergesort_mt(&mut right[..], max_threads, thread_counter);

        return merge(&mut left[..], &mut right[..]);
    } else {
        drop(mutex_value);
        let mut left = mergesort_mt(&mut left[..], max_threads, Arc::clone(&thread_counter));
        let mut right = mergesort_mt(&mut right[..], max_threads, Arc::clone(&thread_counter));
        return merge(&mut left[..], &mut right[..]);
    }
}

Thanks for reading and I appreciate the feedback!

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • 1
    Does this answer your question: [How can I pass a reference to a stack variable to a thread?](/q/32750829/2189130) The answer there does mention rayon and crossbeam, but the first recommendation is to use `thread::scope` from the standard library which lets you use local variables in spawned threads. – kmdreko Feb 19 '23 at 00:01
  • _"using only the standard Rust library"_ -- If you intend to use Rust seriously, you need to get used to using external crates. From your own comments, external crates exactly solve your problem, so why aren't you using them? – Peter Hall Feb 19 '23 at 03:59
  • @kmdreko Thank you, that was exactly what I was looking for. I must have glossed over that when looking at the documentation for the threads module yesterday. – Sean Tronsen Feb 19 '23 at 15:57
  • @PeterHall I understand your point and agree with it too as I'm no stranger to using external libraries. A bit of a more minor example, but for this mini-challenge project, I used the `rand` crate to generate unordered sequences of integers, reproducible using a common seed. I intend to make full use of the crates available on [crates.io](https://crates.io) in time, but I'm limiting myself temporarily until I feel more confident in my skills with the language. Mainly so that I can contribute crates to the registry later on when I find edge cases that haven't been implemented yet. – Sean Tronsen Feb 19 '23 at 16:15

0 Answers0