以这个简单的例子为例,我们使用不可变的向量列表来计算新值。
鉴于此工作,单线程示例:
use std::collections::LinkedList;
fn calculate_vec(v: &Vec<i32>) -> i32 {
let mut result: i32 = 0;
for i in v {
result += *i;
}
return result;
}
fn calculate_from_list(list: &LinkedList<Vec<i32>>) -> LinkedList<i32> {
let mut result: LinkedList<i32> = LinkedList::new();
for v in list {
result.push_back(calculate_vec(v));
}
return result;
}
fn main() {
let mut list: LinkedList<Vec<i32>> = LinkedList::new();
// some arbitrary values
list.push_back(vec![0, -2, 3]);
list.push_back(vec![3, -4, 3]);
list.push_back(vec![7, -10, 6]);
let result = calculate_from_list(&list);
println!("Here's the result!");
for i in result {
println!("{}", i);
}
}
假设calculate_vec
是处理器密集型函数,我们可能希望使用多个线程来运行它,以下示例有效,但需要(我认为是)一个不必要的向量克隆。
use std::collections::LinkedList;
fn calculate_vec(v: &Vec<i32>) -> i32 {
let mut result: i32 = 0;
for i in v {
result += *i;
}
return result;
}
fn calculate_from_list(list: &LinkedList<Vec<i32>>) -> LinkedList<i32> {
use std::thread;
let mut result: LinkedList<i32> = LinkedList::new();
let mut join_handles = LinkedList::new();
for v in list {
let v_clone = v.clone(); // <-- how to avoid this clone?
join_handles.push_back(thread::spawn(move || calculate_vec(&v_clone)));
}
for j in join_handles {
result.push_back(j.join().unwrap());
}
return result;
}
fn main() {
let mut list: LinkedList<Vec<i32>> = LinkedList::new();
// some arbitrary values
list.push_back(vec![0, -2, 3]);
list.push_back(vec![3, -4, 3]);
list.push_back(vec![7, -10, 6]);
let result = calculate_from_list(&list);
println!("Here's the result!");
for i in result {
println!("{}", i);
}
}
此示例有效,但仅在克隆向量时才有效, 但从逻辑上讲,我认为不应该这样做,因为矢量是不可变的。
没有理由每次调用calculate_vec
都需要分配一个新的向量。
这个简单的例子怎么可能是多线程的,而不需要在传递给闭包之前克隆数据?
更新,根据@ ker的建议,working example使用Arc
,但确实需要取得所有权。
注1)我知道有第三方库来处理线程,但是有兴趣知道是否可以使用Rust的标准库。
注2)在线程上有很多类似的问题,但是例子通常涉及线程写入数据,但这不是这种情况。
答案 0 :(得分:1)
有多种方法可以解决您的问题。
将Vector移动到Arc<LinkedList<Vec<i32>>>
并克隆它。在计算之后,您可以使用try_unwrap
来恢复LinkedList<Vec<i32>>
。这适用于Rust标准库。
这是使用Arc
的{{3}},尽管LinkedList
被Vec
替换为允许索引。
另请注意,在这种情况下,函数需要拥有传递给它的参数。
使用working example包创建可引用其范围的线程,使您无需手动执行所有join_handles
代码。这将对您的代码产生最小的影响,因为它的工作方式与您想要的完全一样。
crossbeam::scope(|scope| {
for v in list {
scope.spawn(|| calculate_vec(&v))
}
});
使用crossbeam
包。它的工作方式与crossbeam
类似,但不会为每个任务创建一个线程,而是在有限数量的线程上展开任务。 (感谢@delnan)
使用scoped_threadpool
包进行直接数据并行
use rayon::prelude::*;
list.par_iter().map(|v| calculate_vec(&v)).collect()