访问 par_iter_mut 内部集合中不同索引的字段

问题描述

以下示例说明了我正在尝试做的事情:

use rayon::prelude::*;

struct Parent {
    children: Vec<Child>,}
struct Child {
    value: f64,index: usize,//will keep the distances to other children in the children vactor of parent
    distances: Vec<f64>,}

impl Parent {
    fn calculate_distances(&mut self) {
        self.children
            .par_iter_mut()
            .for_each(|x| x.calculate_distances(&self.children));
    }
}

impl Child {
    fn calculate_distances(&mut self,children: &[Child]) {
        children
            .iter()
            .enumerate()
            .for_each(|(i,x)| self.distances[i] = (self.value - x.value).abs());
    }
}

以上内容无法编译。问题是,我无法在第一个 for_each 的关闭中访问 &self.children。我确实理解,为什么借用检查器不允许这样做,所以我的问题是,是否有办法让它在稍作改动的情况下工作。到目前为止,我发现的解决方案并不令人满意。一种解决方案是在 Parent::calculate distances 的开头克隆孩子并在闭包中使用它(这会增加不必要的克隆)。另一种解决方案是像这样提取 Child 的值字段:

use rayon::prelude::*;
struct Parent {
    children: Vec<Child>,values: Vec<f64>
}
struct Child {
    index: usize,}

impl Parent {
    fn calculate_distances(&mut self) {
      let values = &self.values;
        self.children
            .par_iter_mut()
            .for_each(|x| x.calculate_distances(values));
    }
}

impl Child {
    fn calculate_distances(&mut self,values: &[f64]) {
       for i in 0..values.len(){
           self.distances[i]= (values[self.index]-values[i]).abs();
       }
    }
}

虽然这会很有效,但它完全弄乱了我的真实代码,并且价值在概念上确实属于 Child。我对 Rust 比较陌生,只是问自己是否有任何好的方法可以做到这一点。据我所知,需要一种方法来告诉编译器,我只更改并行迭代器中的距离字段,而值字段保持不变。也许这是一个使用不安全的地方?无论如何,如果您能向我暗示正确的方向,或者至少确认我的代码确实必须变得更加混乱才能使其工作,我将非常感激:)

解决方法

Rust 努力阻止你做你想做的事情:在修改它的同时保留对整个集合的访问。如果您不愿意调整数据布局以适应借用检查器,您可以使用内部可变性使 Child::calculate_distances&self 而不是 &mut self。然后您的问题就会消失,因为分发对 self.children 的多个共享引用是完全没问题的。

理想情况下,您应该使用 RefCell,因为您不会从多个线程访问相同的 Child。但是 Rust 不允许这样做,因为根据所涉及函数的签名,您可以这样做,这将是数据竞争。声明 distances: RefCell<Vec<f64>> 使 Child 不再 Sync,删除对 Vec<Child>::par_iter() 的访问。

您可以做的是使用 Mutex。尽管最初感觉很浪费,但请记住,每次调用 Child::calculate_distances() 都会收到不同的 Child,因此互斥锁将始终是无竞争的,因此锁定成本低(不涉及系统调用)。并且每个 Child::calculate_distances() 只锁定一次,而不是每次访问数组时锁定它。代码如下所示 (playground):

use rayon::prelude::*;
use std::sync::Mutex;

struct Parent {
    children: Vec<Child>,}
struct Child {
    value: f64,index: usize,//will keep the distances to other children in the children vactor of parent
    distances: Mutex<Vec<f64>>,}

impl Parent {
    fn calculate_distances(&mut self) {
        self.children
            .par_iter()
            .for_each(|x| x.calculate_distances(&self.children));
    }
}

impl Child {
    fn calculate_distances(&self,children: &[Child]) {
        let mut distances = self.distances.lock().unwrap();
        children
            .iter()
            .enumerate()
            .for_each(|(i,x)| distances[i] = (self.value - x.value).abs());
    }
}

您也可以尝试用 std::sync::Mutex 替换 parking_lot::Mutex,它更小(只有一个字节的开销,没有分配)、更快,并且不需要 unwrap(),因为它不需要不要锁中毒。