How to modify mutable variable in scoped threadpool?

Simple question, but I can't for the life of me figure out how to do this. I have a variable I want to update inside the thread but the compiler tells me pividx does not live long enough. If possible I'd even like to do this without mutexes, as only one of the threads needs to access pividx.

fn partition_chunks_parallel(arr: &mut [i32], chunk_size: usize, pool: &mut Pool) -> usize {
    let piv = arr[0];
    let pividx = Arc::new(Mutex::new(0));
    pool.scoped(|scope| {
        for (i, chunk) in arr.chunks_mut(chunk_size).enumerate() {
            let pividx_clone = pividx.clone();
            scope.execute(move || {
                let new_pividx = partition_chunk(chunk, piv);
                if i == 0 {
                    *pividx.lock().unwrap() = new_pividx;
                }
            });
        }
    });
    *pividx.lock().unwrap()
}

Can someone help?

Also, if I simply declare pividx as a mut usize, it will let me update inside the thread pool, but tells me the variable is unused. The variable is also not updated. Does that mean a new mutable variable for each thread is created temporarily?

You can't edit an integer from multiple threads without synchronization. Use an atomic integer instead.

1 Like

Now the compiler tells me:

use of moved value: `pividx`

value moved into closure here, in previous iteration of loop

note: move occurs because `pividx` has type `std::sync::atomic::AtomicUsize`, which does not implement the `Copy` traitrustc(E0382)

New Code:

fn partition_chunks_parallel(arr: &mut [i32], chunk_size: usize, pool: &mut Pool) -> usize {
let piv = arr[0];
let mut pividx = AtomicUsize::new(0);
pool.scoped(|scope| {
    for (i, chunk) in arr.chunks_mut(chunk_size).enumerate() {
        scope.execute(move || {
            let new_pividx = partition_chunk(chunk, piv);
            if i == 0 {
                *pividx.get_mut() = new_pividx;
            }
        });
    }
});
pividx.into_inner()
}

You should use store rather than get_mut.

1 Like
let piv = arr[0];
let mut pividx = AtomicUsize::new(0);
let pivdix_ref = &pivdix; // << note the reference 
pool.scoped(|scope| {
    for (i, chunk) in arr.chunks_mut(chunk_size).enumerate() {
        scope.execute(move || {
            let new_pividx = partition_chunk(chunk, piv);
            if i == 0 {
                pividx_ref.store(new_pivdix. Ordering::SeqCst);
            }
        });
    }
});
pividx.into_inner()
}

But you can do better if you are using rayon

let piv = arr[0]; 
pool.scoped(|scope| {
    let mut join_handle = None:
    for (i, chunk) in arr.chunks_mut(chunk_size).enumerate() {
        let jh = scope.spawn(move |scope| {
            let new_pividx = partition_chunk(chunk, piv);
            if i == 0 {
                Some(new_pivdix)
            } else {
                None
            }
        });
        join_handle.get_or_insert(jh);
    }
    join_handle.unwrap().join().unwrap().unwrap()
})
}
2 Likes

Hi, thank you, both of you. Life kept me busy, which is why I'm only responding now. It turned out I actually needed a vector of usize and not just one usize, so I did solve it a little differently using mutexes. Unless I'm wrong and that is also possible with atomics? Nonetheless your answers are very illuminating. I just noticed that the scoped_threadpool library I used is terribly outdated. Next time I'll use rayon.

You can use a vector of atomics instead of a mutex.

1 Like

I would just make the switch to rayon now, it shouldn't be too hard to switch scoped_threadpool::Pool with rayon::ThreadPoolBuilder/rayon::ThreadPool
and scoped_threadpool::Scope::execute with rayon::Scope::spawn

rayon will also give you extra flexibility and configuration options, so the move is definitely worth it.

1 Like