Spawning a single thread is easy. Spawning several and getting their answers back in a predictable order is where people usually slip.
The pattern is two passes. First spawn every thread and collect the handles into a Vec<JoinHandle<T>>. Only then join them, one after another, in that same order. Because the vector remembers which handle came from which input, the results line up with the input regardless of which thread actually finished first.
The order of the two passes matters a lot. If you join inside the loop that spawns, each thread is finished before the next one starts and you have written a sequential loop with extra steps. Spawn first, join second, and the work really does overlap.
Implement two functions.
sum_chunkspub fn sum_chunks(chunks: Vec<Vec<u64>>) -> Vec<u64>Give each chunk its own thread. Return one sum per chunk, in the same order the chunks arrived. An empty chunk sums to 0.
let chunks = vec![
vec![1, 2],
vec![10],
vec![],
];
assert_eq!(sum_chunks(chunks), vec![3, 10, 0]);sum_allpub fn sum_all(chunks: Vec<Vec<u64>>) -> u64Sum the chunks in parallel, then add the partial sums into one grand total.
let chunks = vec![vec![1, 2], vec![3]];
assert_eq!(sum_all(chunks), 6);.map(...).collect::<Vec<_>>(), which forces every thread to start before you touch a single result.handles.into_iter().map(|h| h.join().unwrap()).collect().sum_all can just call sum_chunks and sum the answer. Reusing it is better than writing the spawn loop twice.chunk.iter().sum::<u64>() needs the turbofish because the closure's return type is otherwise ambiguous.Spawning a single thread is easy. Spawning several and getting their answers back in a predictable order is where people usually slip.
The pattern is two passes. First spawn every thread and collect the handles into a Vec<JoinHandle<T>>. Only then join them, one after another, in that same order. Because the vector remembers which handle came from which input, the results line up with the input regardless of which thread actually finished first.
The order of the two passes matters a lot. If you join inside the loop that spawns, each thread is finished before the next one starts and you have written a sequential loop with extra steps. Spawn first, join second, and the work really does overlap.
Implement two functions.
sum_chunkspub fn sum_chunks(chunks: Vec<Vec<u64>>) -> Vec<u64>Give each chunk its own thread. Return one sum per chunk, in the same order the chunks arrived. An empty chunk sums to 0.
let chunks = vec![
vec![1, 2],
vec![10],
vec![],
];
assert_eq!(sum_chunks(chunks), vec![3, 10, 0]);sum_allpub fn sum_all(chunks: Vec<Vec<u64>>) -> u64Sum the chunks in parallel, then add the partial sums into one grand total.
let chunks = vec![vec![1, 2], vec![3]];
assert_eq!(sum_all(chunks), 6);.map(...).collect::<Vec<_>>(), which forces every thread to start before you touch a single result.handles.into_iter().map(|h| h.join().unwrap()).collect().sum_all can just call sum_chunks and sum the answer. Reusing it is better than writing the spawn loop twice.chunk.iter().sum::<u64>() needs the turbofish because the closure's return type is otherwise ambiguous.