如何在不克隆的情况下对只读数据进行多线程函数调用?

时间:2016-09-13 06:24:35

标签: multithreading rust

以这个简单的例子为例,我们使用不可变的向量列表来计算新值。

鉴于此工作,单线程示例:

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)在线程上有很多类似的问题,但是例子通常涉及线程写入数据,但这不是这种情况。

1 个答案:

答案 0 :(得分:1)

有多种方法可以解决您的问题。

  1. 将Vector移动到Arc<LinkedList<Vec<i32>>>并克隆它。在计算之后,您可以使用try_unwrap来恢复LinkedList<Vec<i32>>。这适用于Rust标准库。

    这是使用Arc的{​​{3}},尽管LinkedListVec替换为允许索引。
    另请注意,在这种情况下,函数需要拥有传递给它的参数。

  2. 使用working example包创建可引用其范围的线程,使您无需手动执行所有join_handles代码。这将对您的代码产生最小的影响,因为它的工作方式与您想要的完全一样。

    crossbeam::scope(|scope| {
        for v in list {
            scope.spawn(|| calculate_vec(&v))
        }
    });
    
  3. 使用crossbeam包。它的工作方式与crossbeam类似,但不会为每个任务创建一个线程,而是在有限数量的线程上展开任务。 (感谢@delnan)

  4. 使用scoped_threadpool包进行直接数据并行

    use rayon::prelude::*;
    list.par_iter().map(|v| calculate_vec(&v)).collect()