Rust - Use threads to iterate over a vector

727 Views Asked by At

My program creates a grid of numbers, then based on the sum of the numbers surrounding each on the grid the number will change in a set way. I'm using two vectors currently, filling the first with random numbers, calculating the changes, then putting the new values in the second vector. After the new values go into the second vector I then push them back into the first vector before going through the next loop. The error I get currently is:

error[E0499]: cannot borrow `grid_a` as mutable more than once at a time
  --> src\main.rs:40:29
   |
38 |               thread::scope(|s| {
   |                              - has type `&crossbeam::thread::Scope<'1>`
39 |                   for j in 0..grid_b.len() {
40 |                       s.spawn(|_| {
   |                       -       ^^^ `grid_a` was mutably borrowed here in the previous iteration of the loop
   |  _____________________|
   | |
41 | |                         grid_a[j] = grid_b[j];
   | |                         ------ borrows occur due to use of `grid_a` in closure
42 | |                     });
   | |______________________- argument requires that `grid_a` is borrowed for `'1`

My current code is below. I'm way more familiar with C++ and C#, in the process of trying to learn Rust for this assignment. If I remove the thread everything compiles and runs properly. I'm not understanding how to avoid the multiple borrow. Ideally I'd like to use a separate thread::scope on with the for loop above the existing thread::scope as well.

use crossbeam::thread;
use rand::Rng;
use std::sync::{Arc, Mutex};
use std::thread::sleep;
use std::time::Duration;
use std::time::Instant;

static NUMROWS: i32 = 4;
static NUMCOLUMNS: i32 = 7;
static GRIDSIZE: i32 = NUMROWS * NUMCOLUMNS;
static PLUSNC: i32 = NUMCOLUMNS + 1;
static MINUSNC: i32 = NUMCOLUMNS - 1;
static NUMLOOP: i32 = 7;
static HIGH: u32 = 35;

fn main() {
    let start = Instant::now();
    let length = usize::try_from(GRIDSIZE).unwrap();
    let total_checks = Arc::new(Mutex::new(0));

    let mut grid_a = Vec::<u32>::with_capacity(length);
    let mut grid_b = Vec::<u32>::with_capacity(length);

    grid_a = fill_grid();

    for h in 1..=NUMLOOP {
        println!("-- {} --", h);
        print_grid(&grid_a);
        if h != NUMLOOP {
            for i in 0..grid_a.len() {
                let mut total_checks = total_checks.lock().unwrap();
                grid_b[i] = checker(&grid_a, i.try_into().unwrap());
                *total_checks += 1;
            }
            grid_a.clear();
            thread::scope(|s| {
                for j in 0..grid_b.len() {
                    s.spawn(|_| {
                        grid_a[j] = grid_b[j];
                    });
                }
            })
            .unwrap();

            grid_b.clear();
        }
    }
1

There are 1 best solutions below

0
On

When you access a vector (or any slice) via index you're borrowing the whole vector. You can use iterators which can give you mutable references to all the items in parallel.

use crossbeam::thread;

static NUMROWS: i32 = 4;
static NUMCOLUMNS: i32 = 7;
static GRIDSIZE: i32 = NUMROWS * NUMCOLUMNS;
static NUMLOOP: i32 = 7;

fn fill_grid() -> Vec<u32> {
    (0..GRIDSIZE as u32).into_iter().collect()
}
fn main() {
    let length = usize::try_from(GRIDSIZE).unwrap();

    // just do this since else we create a vec and throw it away immediately
    let mut grid_a = fill_grid();
    let mut grid_b = Vec::<u32>::with_capacity(length);

    for h in 1..=NUMLOOP {
        println!("-- {} --", h);
        println!("{grid_a:?}");
        if h != NUMLOOP {
            // removed a bunch of unrelated code
            for i in 0..grid_a.len() {
                grid_b.push(grid_a[i]);
            }
            // since we overwrite grid_a anyways we don't need to clear it.
            // it would give us headaches anyways since grid_a[j] on an empty
            // Vec panics.
            // grid_a.clear();
            thread::scope(|s| {
                // instead of accessing the element via index we iterate over
                // mutable references so we don't have to borrow the whole
                // vector inside the thread
                for (pa, b) in grid_a.iter_mut().zip(grid_b.iter().copied()) {
                    s.spawn(move |_| {
                        *pa = b + 1;
                    });
                }
            })
            .unwrap();

            grid_b.clear();
        }
    }
} // add missing }