What is the correct way to use multi-threaded parallelism when there is no resource competition

Hi everyone, I want to implement a minimalist multithreaded model in rust. But according to what I've just read about it, it seems that in rust, the copy operation must be performed whenever executing multiple threads in order to ensure safety. (Seems that I have to use arc/rc/lock stuff always)

Assume the following condition, I got a fixed length vector of f64, and I want to calculate the mean, median and standard deviation of this vector in three threads in parallel. Since each thread only reads data from the vector and not modifying it, ideally there's no locks/copys or other overhead is required.

Here's the pseudo-code, how should I complete it? Thanks.

use std::thread;

fn compute_mean(data: &Vec<f64>) -> f64 {
    let result:f64;
    result
}

fn compute_median(data: &Vec<f64>) -> f64 {
}

fn compute_stddev(data: &Vec<f64>) -> f64 {
}

fn main() {
    let data:Vec<f64> = vec![1,2,3,4,5];
    let child = thread::spawn(|data| {
        compute_mean(data)
    });
    child.join();
}

In the standard library, you need something like an Arc to share data across threads:

use std::thread;
use std::sync::Arc;

fn compute_mean(data: &Vec<f64>) -> f64 {
    todo!()
}

fn compute_median(data: &Vec<f64>) -> f64 {
    todo!()
}

fn compute_stddev(data: &Vec<f64>) -> f64 {
    todo!()
}

fn main() {
    let data: Arc<Vec<f64>> = Arc::new(vec![1,2,3,4,5]);
    
    let data1 = Arc::clone(&data);
    let child1 = thread::spawn(|| {
        compute_mean(&data1)
    });
    
    let data2 = Arc::clone(&data);
    let child2 = thread::spawn(|| {
        compute_median(&data2)
    });
    
    let data3 = Arc::clone(&data);
    let child3 = thread::spawn(|| {
        compute_stddev(&data3)
    });
    
    let mean = child1.join();
    let median = child2.join();
    let stddev = child3.join();
}

To be clear, the above snippet does not clone the vector, and it does not involve any locking.

To get around an Arc, you must use a scoped thread, which is provided by crossbeam or rayon crates.

1 Like

Thanks, I got the following error message:

error[E0373]: closure may outlive the current function, but it borrows `data1`, which is owned by the current function
  --> src\main.rs:36:32
   |
36 |     let child1 = thread::spawn(|| {
   |                                ^^ may outlive borrowed value `data1`
37 |         compute_mean(&data1)
   |                       ----- `data1` is borrowed here
   |
note: function requires argument type to outlive `'static`
  --> src\main.rs:36:18
   |
36 |       let child1 = thread::spawn(|| {
   |  __________________^
37 | |         compute_mean(&data1)
38 | |     });
   | |______^
help: to force the closure to take ownership of `data1` (and any other referenced variables), use the `move` keyword

so I changed let child1 = thread::spawn(|| { into let child1 = thread::spawn(move || {, but I cant understand why this make difference.

Ah yeah, it was missing a move on the ||.

It will force the spawned thread to take ownership of the Arc rather than store a reference to the Arc that is kept on the main thread's stack. Rust won't allow the spawned thread to store references to values on the main thread's stack because spawned threads are allowed to live longer than their parent thread's stack.

1 Like

@alice @Kestrer Thanks guys, I've raised a new topic with new requirements, appreciated if you could help.