Struggling with a method doing a computation in threads

Hi, I just started to learn Rust. For my toy project I've picked to write a linear algebra library. I would like to run some computations in parallel but I've ran into some troubles.

  • Will different threads run on different cores or on one? In other words, just using threads will I have true parallelism or just a concurrency?
  • My code doesn't compile. I've made a toy snippet just for this post that aims to compute an inner product of two vectors but with a separate thread for every term in the product.
use std::sync::{Arc, Mutex};
use std::thread;

struct Vector {
    _data: Arc<Mutex<Vec<f64>>>,
    pub m: usize,
}

impl Vector {
    fn new(d: Vec<f64>) -> Vector {
        let m = d.len();
        Vector {
            _data: Arc::new(Mutex::new(d)),
            m: m,
        }
    }

    fn inner(&self, v: &Vector) -> f64 {
        assert!(self.m == v.m);
        let mut val = 0.;
        let val_ref = Arc::new(Mutex::new(&val));
        let mut threads = vec![];

        for i in 0..self.m {
            let u_data = self._data.clone();
            let v_data = v._data.clone();
            let val_inner = val_ref.clone();
            threads.push(thread::spawn(move || {
                let u_loc = u_data.lock().unwrap();
                let v_loc = v_data.lock().unwrap();
                let mut val_loc = val_inner.lock().unwrap();
                **val_loc += u_loc[i] * v_loc[i];
            }));
        }
        for thread in threads { 
            thread.join().unwrap();
        }
        val
    }
}
    
fn main() { 
    let v = Vector::new(vec![1., 2.]);
    let u = Vector::new(vec![-2., 2.]);
    assert_eq!(u.inner(&v), 2.);
}

I get the following compile error:

error[E0594]: cannot assign to data in a dereference of `std::sync::MutexGuard<'_, &f64>`
  --> src/main.rs:32:17
   |
32 |                 **val_loc += u_loc[i] * v_loc[i];
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot assign
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `std::sync::MutexGuard<'_, &f64>`

warning: variable does not need to be mutable
  --> src/main.rs:20:13
   |
20 |         let mut val = 0.;
   |             ----^^^
   |             |
   |             help: remove this `mut`
   |
   = note: `#[warn(unused_mut)]` on by default

error[E0597]: `val` does not live long enough
  --> src/main.rs:21:43
   |
21 |         let val_ref = Arc::new(Mutex::new(&val));
   |                                -----------^^^^-
   |                                |          |
   |                                |          borrowed value does not live long enough
   |                                argument requires that `val` is borrowed for `'static`
...
39 |     }
   |     - `val` dropped here while still borrowed

error: aborting due to 2 previous errors

What is the right and idiomatic way to do this? Also why does the code compile, when I remove the return variable, i.e when every thread computes the product of two numbers and just drops it. I would expect the compiler complain about the lifetimes of the vectors. Does it use the "handle.join()" block to realize that the self and v object outlive the threads?

Rust uses the operating system’s native thread implementation, so it should run multi-core unless you are running in an unusual environment (like something embedded).


There are two related issues with your code, both caused by trying to hold an &f64 inside inner’s Mutex:

  • Because this is an immutable reference, you can’t store into its result. For that, you’d need an &mut f64 instead.
  • You can’t pass anything containing a temporary reference to a new thread, because the borrow checker can’t verify that the thread terminates before the reference becomes invalid. val is a local variable, and the compiler isn’t smart enough to verify that the child threads don’t store a reference to it somewhere.

If the Mutex holds the result value directly instead of a reference, it works:

    fn inner(&self, v: &Vector) -> f64 {
        assert!(self.m == v.m);
        let val = Arc::new(Mutex::new(0f64));
        let mut threads = vec![];

        for i in 0..self.m {
            let u_data = self._data.clone();
            let v_data = v._data.clone();
            let val_inner = val.clone();
            threads.push(thread::spawn(move || {
                let u_loc = u_data.lock().unwrap();
                let v_loc = v_data.lock().unwrap();
                let mut val_loc = val_inner.lock().unwrap();
                *val_loc += u_loc[i] * v_loc[i];
            }));
        }
        for thread in threads { 
            thread.join().unwrap();
        }
        return *val.lock().unwrap();
    }

(Playground)

1 Like

Thank you very much. It works! I've tried this before, with one exception - instead of 'return *val.lock().unwrap();' I had only '*val.lock().unwrap()' and got a compilation error. Seems I missed some vital detail about the return keyword as I thought it's the same as just having the last block of code to be an expression.

I ran into that as well; it’s a subtle problem. Normally, when you end a code block with an expression, Rust doesn’t free the temporary values in that expression until the end of the containing statement; that mechanism caused some problems here.

This compiler message is what prompted me to try return instead:


   = note: the temporary is part of an expression at the end of a block;
           consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
   |
37 |         let x = *val.lock().unwrap(); x
   |         ^^^^^^^                     ^^^
1 Like