我希望能够让多个线程评估相同的闭包。我想到的应用程序是并行化数值集成,因此可以将函数域轻松拆分为N个块并传递给线程。
这是一个简单的函数,它多次计算提供的闭包并平均结果:
use std::sync::mpsc;
use std::thread;
const THREAD_COUNT: u64 = 4;
fn average<F: Fn(f64) -> f64>(f: F) -> f64 {
let (tx, rx) = mpsc::channel();
for id in 0..THREAD_COUNT {
let thread_tx = tx.clone();
thread::spawn(move || {
thread_tx.send(f(id as f64));
});
}
let mut total = 0.0;
for id in 0..THREAD_COUNT {
total += rx.recv().unwrap();
}
total / THREAD_COUNT as f64
}
fn main() {
average(|x: f64| -> f64 { x });
}
编译时我收到此错误:
error[E0277]: `F` cannot be sent between threads safely
--> src/main.rs:10:9
|
10 | thread::spawn(move || {
| ^^^^^^^^^^^^^ `F` cannot be sent between threads safely
|
= help: within `[closure@src/main.rs:10:23: 12:10 thread_tx:std::sync::mpsc::Sender<f64>, f:F, id:u64]`, the trait `std::marker::Send` is not implemented for `F`
= help: consider adding a `where F: std::marker::Send` bound
= note: required because it appears within the type `[closure@src/main.rs:10:23: 12:10 thread_tx:std::sync::mpsc::Sender<f64>, f:F, id:u64]`
= note: required by `std::thread::spawn`
所以我将+ Send
添加到F
的边界并收到新错误:
error[E0310]: the parameter type `F` may not live long enough
--> src/main.rs:10:9
|
6 | fn average<F: Fn(f64) -> f64 + Send>(f: F) -> f64 {
| -- help: consider adding an explicit lifetime bound `F: 'static`...
...
10 | thread::spawn(move || {
| ^^^^^^^^^^^^^
|
note: ...so that the type `[closure@src/main.rs:10:23: 12:10 thread_tx:std::sync::mpsc::Sender<f64>, f:F, id:u64]` will meet its required lifetime bounds
--> src/main.rs:10:9
|
10 | thread::spawn(move || {
| ^^^^^^^^^^^^^
所以我将+ 'static
添加到F
并获取此信息:
error[E0382]: capture of moved value: `f`
--> src/main.rs:11:28
|
10 | thread::spawn(move || {
| ------- value moved (into closure) here
11 | thread_tx.send(f(id as f64));
| ^ value captured here after move
|
= note: move occurs because `f` has type `F`, which does not implement the `Copy` trait
所以我将+ Copy
添加到F
并获取:
error: the trait `core::marker::Copy` is not implemented for the type `[closure@src/test.rs:115:11: 115:26]
似乎每个线程都需要自己的闭包副本(因为move
)但是闭包没有实现Copy
所以没有运气。这对我来说似乎很奇怪,因为如果闭包从不变态,那么多线程访问它们的安全问题是什么?
我可以通过提供常规函数而不是闭包来使代码工作,但这使得我的代码不是通用的,即它只适用于特定函数,而不适用于Fn(f64) -> f64
的任何函数。对于我正在进行的集成类型,集成的函数通常将某些固定变量与集成变量混合在一起,因此使用闭包捕获固定变量似乎很自然。
有没有办法让这种多线程功能评估以通用方式工作?我只是在想错事吗?
答案 0 :(得分:6)
最终问题围绕着拥有闭包的。所写的代码表明闭包的所有权转移到average
。然后,此函数尝试将闭包赋予多个线程,如您所见,该线程失败,因为您无法将一个项目提供给多个子项。
但是闭包没有实现
Copy
所以没有运气
从Rust 1.26.0开始,如果所有捕获的变量都有,那么闭包 实现Clone
和Copy
。这意味着您的最终示例代码现在可以按原样运行:
fn average<F: Fn(f64) -> f64 + Send + 'static + Copy>(f: F) -> f64 { /* ... */ }
但是,您的闭包可能无法实现Copy
或Clone
。
您无法提供average
拥有的闭包的引用,因为使用thread::spawn
创建的主题可能比average
的调用更长。当average
退出时,任何堆栈分配的变量都将被销毁。任何使用它们都会导致记忆不安全,Rust会阻止这种记忆。
一种解决方案是使用Arc
。这将允许多线程上下文中的单个资源的多个共享所有者。克隆包装闭包时,只创建一个新引用。当所有引用消失时,对象将被释放。
use std::{
sync::{mpsc, Arc},
thread,
};
const THREAD_COUNT: u64 = 4;
fn average<F>(f: F) -> f64
where
F: Fn(f64) -> f64 + Send + Sync + 'static,
{
let (tx, rx) = mpsc::channel();
let f = Arc::new(f);
for id in 0..THREAD_COUNT {
let thread_tx = tx.clone();
let f = f.clone();
thread::spawn(move || {
thread_tx.send(f(id as f64)).unwrap();
});
}
let mut total = 0.0;
for _ in 0..THREAD_COUNT {
total += rx.recv().unwrap();
}
total / THREAD_COUNT as f64
}
fn main() {
average(|x| x);
}
更标准的解决方案是使用作用域线程。这些线程保证在一定时间内退出,这允许您传递比线程更长的引用到线程。
另见: