将 iter() 替换为 par_iter():不能在“Fn”闭包中捕获的外部变量中可变地借用数据

Replace iter() with par_iter(): cannot borrow data mutably in a captured outer variable in an `Fn` closure

提问人:manonthemat 提问时间:10/26/2017 最后编辑:Shepmastermanonthemat 更新时间:10/13/2023 访问量:2251

问:

我希望在这样相当简单的情况下用人造丝替换,但我没有这样做。iter()par_iter()

前面的代码:

indexes_to_increment
    .iter()
    .for_each(|x| self.some_data[*x as usize] += 1);`

下面是 Rayon 修改后的代码:

extern crate rayon;
use rayon::prelude::*;

fn main() {
    let mut a = SomeStruct::new(vec![1, 0, 0, 1]);
    a.add_factor_indexes(&vec![1, 2]);
    println!("{:?}", a); // spits out "SomeStruct { some_data: [1, 1, 1, 1] }"
}

#[derive(Debug)]
struct SomeStruct {
    some_data: Vec<u8>,
}

impl SomeStruct {
    fn new(some_data: Vec<u8>) -> SomeStruct {
        SomeStruct { some_data }
    }
    fn add_factor_indexes(&mut self, indexes_to_increment: &[u8]) {
        //indexes_to_increment.iter().for_each(|x| self.some_data[*x as usize] += 1);
        indexes_to_increment
            .par_iter()
            .for_each(|x| self.some_data[*x as usize] += 1);
    }
}

(操场)

虽然我知道以下错误消息告诉我该怎么做,但此时我无法这样做。

error[E0387]: cannot borrow data mutably in a captured outer variable in an `Fn` closure
  --> src/main.rs:23:27
   |
23 |             .for_each(|x| self.some_data[*x as usize] += 1);
   |                           ^^^^^^^^^^^^^^
   |
help: consider changing this closure to take self by mutable reference
  --> src/main.rs:23:23
   |
23 |             .for_each(|x| self.some_data[*x as usize] += 1);
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

如果我知道 in 中的向量只包含唯一的 s,并且可以用集合替换,这会改变什么吗?indexes_to_incrementadd_factor_indexesu8

色人造丝

评论


答:

8赞 Shepmaster 10/26/2017 #1

此错误消息 Rust 旨在为您提供的错误预防类型的一个示例。换句话说,编译器阻止您同时以可变方式访问同一块内存。

从概念上讲,你尝试运行的代码应该是安全的,因为你总是在访问向量的一个完全不相交的部分,不会有任何重叠的可变借用同一个索引,但编译器无法判断这一点。它所看到的只是被多次可变地借用;它不知道 的实现做了什么,也不知道闭包的主体做了什么。self.some_dataIndex

您可以在向量中找到所有匹配的插槽,然后遍历所有结果:

fn add_factor_indexes(&mut self, indexes_to_increment: &[u8]) {
    self.some_data
        .par_iter_mut()
        .enumerate()
        .filter(|&(i, _)| indexes_to_increment.contains(&(i as u8)))
        .map(|(_, v)| v)
        .for_each(|x| *x += 1);
}

并且可以用一套代替

如果数据量较大,我会推荐它,因为需要反复查找。

3赞 user4815162342 10/29/2017 #2

当线程不共享非常量数据时,人造丝效果最佳。例如,如果每个闭包都只对自己的数据进行操作,并且在最后一步中将它们重新组合在一起,那么 Rayon 就不会抱怨。(Google MapReduce是这种策略的一个流行示例,可以很好地扩展到云系统。par_iter

除了 Shepmaster 提供的解决方案之外,修复代码的一种直接方法是从 切换到 ,并使用该方法递增索引。由于接受共享引用,因此调用它的闭包将与 Rayon 兼容。Vec<u8>Vec<AtomicUsize>fetch_addfetch_addFn

use rayon::prelude::*;
use std::sync::atomic::{AtomicUsize, Ordering};

fn main() {
    let mut a = SomeStruct::new(
        [1, 0, 0, 1]
            .iter()
            .map(|n| AtomicUsize::new(*n as usize))
            .collect(),
    );
    a.add_factor_indexes(&vec![1, 2]);
    println!("{:?}", a);
}

#[derive(Debug)]
struct SomeStruct {
    some_data: Vec<AtomicUsize>,
}

impl SomeStruct {
    fn new(some_data: Vec<AtomicUsize>) -> SomeStruct {
        SomeStruct { some_data }
    }

    fn add_factor_indexes(&mut self, indexes_to_increment: &[u8]) {
        indexes_to_increment.par_iter().for_each(|&x| {
            self.some_data[x as usize].fetch_add(1, Ordering::Relaxed);
        });
    }
}

操场