在 Programming Rust 的第 465 页上,您可以找到代码和解释(强调由我添加(
use std::sync::Arc; fn process_files_in_parallel(filenames: Vec<String>, glossary: Arc<GigabyteMap>) -> io::Result<()> { ... for worklist in worklists { // This call to .clone() only clones the Arc and bumps the // reference count. It does not clone the GigabyteMap. let glossary_for_child = glossary.clone(); thread_handles.push( spawn(move || process_files(worklist, &glossary_for_child)) ); } ... }
我们更改了词汇表的类型:要并行运行分析,调用方必须通过执行
Arc::new(giga_map)
传入Arc<GigabyteMap>
,即指向已移动到堆中的GigabyteMap
的智能指针。当我们调用 glossary.clone(( 时,我们正在复制Arc
智能指针,而不是整个GigabyteMap
。这相当于递增引用计数。通过此更改,程序将编译并运行,因为它不再依赖于引用生存期。只要任何线程拥有Arc<GigabyteMap>
,它就会保持映射的活力,即使父线程提前退出。不会有任何数据争用,因为Arc
中的数据是不可变的。
在下一节中,他们展示了用人造丝重写的内容,
extern crate rayon; use rayon::prelude::*; fn process_files_in_parallel(filenames: Vec<String>, glossary: &GigabyteMap) -> io::Result<()> { filenames.par_iter() .map(|filename| process_file(filename, glossary)) .reduce_with(|r1, r2| { if r1.is_err() { r1 } else { r2 } }) .unwrap_or(Ok(())) }
您可以在重写为使用 Rayon 的部分中看到它接受&GigabyteMap
而不是Arc<GigabyteMap>
。不过,他们没有解释这是如何工作的。为什么人造丝不需要Arc<GigabyteMap>
?人造丝如何摆脱接受直接参考?
Rayon 可以保证迭代器不会超过当前堆栈帧,这与我在第一个代码示例中假设thread::spawn
不同。具体来说,引擎盖下的par_iter
使用类似于 Rayon 的scope
函数,它允许生成一个"附加"到堆栈的工作单元,并在堆栈结束之前加入。
因为 Rayon 可以保证(通过生命周期边界,从用户的角度来看(在函数调用par_iter
退出之前连接任务/线程,所以它可以提供这个比标准库thread::spawn
更符合人体工程学的 API。
Rayon 在scope
函数的文档中对此进行了扩展。