Accessing variables from threads


#1

The following program:

fn main() {
    let mut a = [0; 5];
    for i in 0..a.len() {
        a[i] = 1;
    }
    println!("{:?} ", a);
}

prints

[1, 1, 1, 1, 1]

as expected.

Instead, if every assignment is put in a distinct thread, like in the following program:

fn main() {
    let mut a = [0; 5];
    let mut thr = vec![];
    for i in 0..a.len() {
        thr.push(std::thread::spawn(move || {
            a[i] = 1;
        }));
    }
    println!("{:?} ", a);
    for t in thr { t.join().unwrap(); }
    println!("{:?} ", a);
}

the following output results:

[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]

How come the assignments have no effect, and no error nor warning is emitted by the compiler?

In general, I would like to read some thorough explanation of how to use threads in Rust, with examples of the typical patterns. The official documentation is quite skimpy.


#2

The store a[i] = 1; is dead: you marked the closure move, so every thread gets its own copy of a.

In terms of diagnostics, ideally rustc would have some sort of dead store warning, but that doesn’t exist at the moment. Not sure if there’s a bug filed.

In terms of documentation, have you read https://doc.rust-lang.org/nightly/book/concurrency.html ? That goes over most of the basics.

Also, https://crates.io/crates/crossbeam is useful for this sort of construct; see http://aturon.github.io/crossbeam-doc/crossbeam/struct.Scope.html#method.spawn .


#3

To expand, what you want is to pass a reference to the array to each thread, but instead you pass the array itself, and arrays of integers are Copy, so it just gets copied (if it were a vector of integers, you’d get an ownership error, because vectors are not Copy).

The reason you can’t do this with thread::spawn is because the API doesn’t guarantee that the spawned thread will die before the array is freed. The scoped thread API in crossbeam does guarantee that.

However, you will not be able to pass a mutable reference to the array in the scoped thread API, because that would allow you to create a data race (e.g. imagine if the closure’s body was a[0] = i instead of a[i] = 1). You instead probably want to iterate over a like this:

for x in &mut a {
    // spawn a scoped thread in which you do *x = 1
}

Because of the iterator interface, x is guaranteed not to overlap with other references into the array.

Lastly, there is another library called rayon which has an API specifically for dividing up the work performed in an iterator on multiple threads. Using the parallel iterator interface in rayon, it will automatically perform the work on each iteration in parallel, using a thread pool and a work stealing algorithm to make more effective use of your machine’s resources than just spawning a thread for each item in the collection.


#4

Just in addition to what @eefriedman and @withoutboats said:

There is also the crate jobsteal that now also supports parallel iteration.

I’ve written a parallel mandelbrot program that shows how to use various crates. You can just read through the source code to get an idea. (I need to update it, hopefully I have more time soon…)