-1

How do I move a vector reference into threads? The closest I get is the (minimized) code below. (I realize that the costly calculation still isn't parallel, as it is locked by the mutex, but one problem at a time.)

Base problem: I'm calculating values based on information saved in a vector. Then I'm storing the results as nodes per vector element. So vector in vector (but only one vector in the example code below). The calculation takes time so I would like to divide it into threads. The structure is big, so I don't want to copy it.

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

fn main() {
    let n = Nodes::init();
    n.calc();
    println!("Result: nodes {:?}", n);
}

#[derive(Debug)]
struct Nodes {
    nodes: Vec<Info>,
}

impl Nodes {
    fn init() -> Self {
        let mut n = Nodes { nodes: Vec::new() };
        n.nodes.push(Info::init(1));
        n.nodes.push(Info::init(2));
        n
    }
    fn calc(&self) {
        Nodes::calc_associative(&self.nodes);
    }
    fn calc_associative(nodes: &Vec<Info>) {
        let mut handles = vec![];
        let arc_nodes = Arc::new(nodes);
        let counter = Arc::new(Mutex::new(0));

        for _ in 0..2 {
            let arc_nodes = Arc::clone(&arc_nodes);
            let counter = Arc::clone(&counter);

            let handle = thread::spawn(move || {
                let mut idx = counter.lock().unwrap();
                // costly calculation
                arc_nodes[*idx].set_length(arc_nodes[*idx].get_length() * 2);
                *idx += 1;
            });
            handles.push(handle);
        }

        for handle in handles {
            handle.join().unwrap();
        }
    }
}

#[derive(Debug)]
struct Info {
    length: u32,
}

impl Info {
    fn init(length: u32) -> Self {
        Info { length }
    }

    fn get_length(&self) -> u32 {
        self.length
    }

    fn set_length(&mut self, x: u32) {
        self.length = x;
    }
}

The compiler complains that life time of the reference isn't fulfilled, but isn't that what Arc::clone() should do? Then Arc require a deref, but maybe there are better solutions before starting to dig into that...?

   Compiling threads v0.1.0 (/home/freefox/proj/threads)
error[E0596]: cannot borrow data in an `Arc` as mutable
  --> src/main.rs:37:17
   |
37 |                 arc_nodes[*idx].set_length(arc_nodes[*idx].get_length() * 2);
   |                 ^^^^^^^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<&Vec<Info>>`

error[E0521]: borrowed data escapes outside of associated function
  --> src/main.rs:34:26
   |
25 |       fn calc_associative(nodes: &Vec<Info>) {
   |                           -----  - let's call the lifetime of this reference `'1`
   |                           |
   |                           `nodes` is a reference that is only valid in the associated function body
...
34 |               let handle = thread::spawn(move || {
   |  __________________________^
35 | |                 let mut idx = counter.lock().unwrap();
36 | |                 // costly calculation
37 | |                 arc_nodes[*idx].set_length(arc_nodes[*idx].get_length() * 2);
38 | |                 *idx += 1;
39 | |             });
   | |              ^
   | |              |
   | |______________`nodes` escapes the associated function body here
   |                argument requires that `'1` must outlive `'static`

Some errors have detailed explanations: E0521, E0596.
For more information about an error, try `rustc --explain E0521`.
error: could not compile `threads` due to 2 previous errors
Robert Locke
  • 455
  • 4
  • 7
  • 2
    Nitpick: Take `&[T]`, not `&Vec`. See [Why is it discouraged to accept a reference to a String (&String), Vec (&Vec), or Box (&Box) as a function argument?](https://stackoverflow.com/questions/40006219/why-is-it-discouraged-to-accept-a-reference-to-a-string-string-vec-vec-o) – Chayim Friedman Dec 29 '22 at 23:29
  • You could pass in `Arc>` and then it'd all work out, passing the responsibility for that up the chain. At some point you might need to use either `Arc>>` or instead use a builder pattern, then lock the final constructed version into an `Arc`. – tadman Dec 29 '22 at 23:36

1 Answers1

1

You wrap a reference with Arc. Now the type is Arc<&Vec<Info>>. There is still a reference here, so the variable could still be destroyed before the thread return and we have a dangling reference.

Instead, you should take a &Arc<Vec<Info>>, and on the construction of the Vec wrap it in Arc, or take &[Info] and clone it (let arc_nodes = Arc::new(nodes.to_vec());). You also need a mutex along the way (either Arc<Mutex<Vec<Info>>> or Arc<Vec<Mutex<Info>>>), since you want to change the items.

Or better, since you immediately join() the threads, use scoped threads:

fn calc_associative(nodes: &[Mutex<Info>]) {
    let counter = std::sync::atomic::AtomicUsize::new(0); // Changed to atomic, prefer it to mutex wherever possible

    std::thread::scope(|s| {
        for _ in 0..2 {
            s.spawn(|| {
                let idx = counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
                let node = &mut *nodes[idx].lock().unwrap();
                // costly calculation
                node.set_length(node.get_length() * 2);
            });
        }
    });
}
Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77