功能说明
这段代码实现一个不依赖第三方运行时的作用域线程并行映射:把输入切片分成若干块,每块交给一个线程执行转换函数,最后按块顺序合并结果。它适合 CPU 密集的纯函数处理,例如哈希、解析、图像像素变换或批量计算。
std::thread::scope 允许线程借用栈上的数据,因此不需要 Arc 或 'static 约束,也不需要使用 unsafe。结果按块顺序拼接,所以输出顺序与输入一致,不会因为线程调度而乱序。
关键代码
parallel_map 用 items.len().div_ceil(workers) 计算块大小,保证块数不超过线程数;空输入时块大小为 1,循环不会产生任务。thread::scope 内为每个块调用 scope.spawn,闭包捕获块的引用和转换函数的引用。
每个线程返回 Vec<U>,主线程按 handles 的顺序 join 并 extend 到结果中。join().expect(...) 会把工作线程的 panic 传播为当前线程的 panic,避免静默丢失数据。
使用注意
线程数不是越多越好,通常设为 CPU 核心数;对 IO 密集任务应使用异步运行时或专用线程池。transform 必须是 Sync,因为多个线程同时持有它的引用;如果它内部有可变状态,需要加锁或改成每个线程独立副本。
块大小固定会导致尾部负载不均,任务耗时差异大时应使用工作窃取或动态分块。join 按顺序等待,如果第一块特别慢,后续结果会先完成但无法提前处理;需要流式消费时应改用通道。结果向量预先分配了总容量,内存占用与输入规模成正比。
验证方式
示例把 1 到 10 并行平方,断言结果与顺序执行的 [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] 完全一致。空切片返回空向量,字符串切片返回对应长度 [1, 2, 3]。可以把 workers 改成 1 或大于元素个数,确认结果不变。
代码示例
use std::thread;
fn parallel_map<T, U, F>(items: &[T], workers: usize, transform: F) -> Vec<U>
where
T: Sync,
U: Send,
F: Fn(&T) -> U + Sync,
{
assert!(workers > 0, "workers must be positive");
let chunk_size = items.len().div_ceil(workers).max(1);
let mut results = Vec::with_capacity(items.len());
thread::scope(|scope| {
let mut handles = Vec::new();
for chunk in items.chunks(chunk_size) {
let transform = &transform;
handles.push(scope.spawn(move || {
chunk.iter().map(|item| transform(item)).collect::<Vec<U>>()
}));
}
for handle in handles {
results.extend(handle.join().expect("worker panicked"));
}
});
results
}
fn main() {
let numbers: Vec<u64> = (1..=10).collect();
let squares = parallel_map(&numbers, 3, |number| number * number);
println!("{squares:?}");
assert_eq!(squares, vec![1, 4, 9, 16, 25, 36, 49, 64, 81, 100]);
let empty: Vec<u64> = parallel_map(&[] as &[u64], 4, |number| number + 1);
assert!(empty.is_empty());
let words = ["a", "bb", "ccc"];
let lengths = parallel_map(&words, 8, |word| word.len());
assert_eq!(lengths, vec![1, 2, 3]);
}