multithreading - 有没有办法让 Rust 闭包只将一些变量移入其中?

标签 multithreading rust closures move-semantics lifetime

我有一个通用的 struct,其中包含设置和一个额外的变量设置,我想对其进行调整和使用。

对于整数范围内的所有可能值,我想启动一个(范围)线程,并将此变量设置为该值。根据这个值,它们的工作略有不同。

这些线程中的每一个都应该能够读取通用设置结构。

use crossbeam; // 0.7.3

struct Settings {
    // ... many fields
}

const MAX_FEASIBLE_SCORE: u8 = 10;

fn example(settings: Settings) {
    crossbeam::scope(|scope| {
        for score in 0..MAX_FEASIBLE_SCORE {
            scope.spawn(|_| {
                let work_result = do_cool_computation(&settings, score);
                println!("{:?}", work_result);
            });
        }
    })
    .unwrap();
}

fn do_cool_computation(_: &Settings, _: u8) {}

这不编译:

error[E0373]: closure may outlive the current function, but it borrows `score`, which is owned by the current function
  --> src/lib.rs:12:25
   |
10 |     crossbeam::scope(|scope| {
   |                       ----- has type `&crossbeam_utils::thread::Scope<'1>`
11 |         for score in 0..MAX_FEASIBLE_SCORE {
12 |             scope.spawn(|_| {
   |                         ^^^ may outlive borrowed value `score`
13 |                 let work_result = do_cool_computation(&settings, score);
   |                                                                  ----- `score` is borrowed here
   |
note: function requires argument type to outlive `'1`
  --> src/lib.rs:12:13
   |
12 | /             scope.spawn(|_| {
13 | |                 let work_result = do_cool_computation(&settings, score);
14 | |                 println!("{:?}", work_result);
15 | |             });
   | |______________^
help: to force the closure to take ownership of `score` (and any other referenced variables), use the `move` keyword
   |
12 |             scope.spawn(move |_| {
   |                         ^^^^^^^^

这将使 &settings 无效,因为第一个循环迭代将在 move 闭包中获取 settings 的所有权。

让它工作的唯一简单方法是:

  • Settings 结构复制到每个线程中(这在我的实际应用程序中相当昂贵)
  • settings 周围引入了一个Arc,这也感觉有点不幸。

有什么办法可以绕过引用计数吗?有没有一种方法可以将 score move 到内部闭包中,同时仍允许引用 settings

最佳答案

是的,可以只将一个或一些变量 move 到闭包中(而不是全部或不 move )。

是的,这可以用来“规避”引用计数。

我在 rayon::scope 的文档中找到了答案事实证明正是关于这个问题:“访问堆栈数据[从范围内的线程范围内]”。该页面还有一个示例,比这个问题中的伪代码更清晰。

事实证明,您可以按如下方式解决此问题:

使用 move 闭包,但通过用引用遮蔽它们来引用外部作用域中的变量,因此通过引用而不是通过值来捕获它们,使用 let settings = &settings:

```
crossbeam::scope(|scope| {
    let settings = &settings; // refer to outer variable by reference
    for score in 0..MAX_FEASIBLE_SCORE {
        scope.spawn(move |_| {
            let work_result = do_cool_computation(settings, score);
            println!("{:?}", work_result);
        });
    }
})
.unwrap();
```

Playground 所以在这里,我们 move 了'all used variables'但是预先把settings变成了一个引用,所以它被借用了。 (迂腐地:我们“move 引用”,但这正是“借用”的意思。)

还有第二种可能:借用所有变量(不 move 任何东西)。

这种可能性适用于许多情况,但不适用于此处。 (因为在这里,我们必须按值捕获 score,因为在 for 循环的下一次迭代和传递给 scope.spawn 的闭包期间,它不会存在 会比这个长寿)。

摘自 rayon::scope 的文档

use rayon;

fn main() {
let ok: Vec<i32> = vec![1, 2, 3];
rayon::scope(|s| {
    let bad: Vec<i32> = vec![4, 5, 6];
    s.spawn(|_| {
        // Transfer ownership of `bad` into a local variable (also named `bad`).
        // This will force the closure to take ownership of `bad` from the environment.
        let bad = bad;
        println!("ok: {:?}", ok); // `ok` is only borrowed.
        println!("bad: {:?}", bad); // refers to our local variable, above.
    });

    s.spawn(|_| println!("ok: {:?}", ok)); // we too can borrow `ok`
});
}

Playground

关于multithreading - 有没有办法让 Rust 闭包只将一些变量移入其中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58459643/

相关文章:

c++ - 使用指针线程化共享模型

java - 线程化数据集?

c - 有没有一种程序化的方法来检查堆栈损坏

iterator - 如何将正在迭代的迭代器传递给函数?

javascript - 有人可以向我解释这个 JavaScript 函数的流程吗? (关闭概念)

java - Java中如何让线程池在不同的核心上运行作业?

rust - 如何使用 zbus 列出 block 设备?

rust - `HashMap::get_mut` 导致 "returns reference to local value",有有效的解决方法吗?

c# - 确定何时将 lambda 编译为实例方法

javascript - 如何从闭包函数调用中返回相同的值 n 次?