我尝试在我实现的一个小型光线跟踪器中使用并行化。
我的想法是使用函数cast_ray()
逐个像素地生成图像,因此我希望为每个像素生成一个新线程以加快计算速度。
基本上,我是在计算一个给定大小的2D数组。我使用消息来更新一个带有颜色值的数组。
我的渲染器函数如下所示(为了清晰起见,我删除了一些部分):
pub fn render_parallel(scene: Scene) -> Vec<Vec<(u8, u8, u8)>> {
// A small 100x100 2D array
let mut image = vec![vec![(0, 0, 0); 100]; 100];
let (tx, rx) = mpsc::channel();
for x in -50..50 {
for y in -50..50 {
// Clone the message emitter
let tx_cell = tx.clone();
// Spawn a new thread for the pixel
thread::spawn(move || {
let ray = ... // A new ray from the camera ;
let color = cast_ray(
&ray,
&scene);
// Send the coordinates & values to the receiver
tx_cell.send((
((24 - y) as usize, (25 + x) as usize),
(color.r, color.g, color.b)))
.unwrap();
});
}
}
for ((i, j), (r, g, b)) in rx {
image[i][j] = (r, g, b)
}
image
}
pub struct Scene {
pub primitives: Vec<Box<dyn Primitive + Sync>>,
pub lights: Vec<Box<dyn Light + Sync>>,
}
pub trait Primitive : Send + Sync {
// Some functions...
}
pub trait Light: Send + Sync {
// Some functions...
}
问题是,它不起作用,Rust给了我下面的错误:
413 | pub fn render_parallel(scene: Scene) -> Vec<Vec<(u8, u8, u8)>>{
| ----- move occurs because `scene` has type `Scene`, which does not implement the `Copy` trait
...
421 | thread::spawn(move || {
| ^^^^^^^ value moved into closure here, in previous iteration of loop
...
428 | &scene,
| ----- use occurs due to use in closure
我不明白为什么在我只使用对它的引用时移动值。我的另一个不使用线程的render()
版本和上面的一样,没有tx
,rx
和thread::spawn()
,没有遇到任何问题,这让我更加困惑。
如何在多个同时闭包中使用scene
变量(如果我正确理解了这个问题的话)?
1条答案
按热度按时间qxsslcnc1#
scene
被移到闭包中,因为您指定了move
。要借用scene
,您需要在派生线程之前将其重新绑定为引用,例如:这会将该作用域中的
scene
重新绑定为对外部scene
的引用,然后将该引用移动到闭包中。然而,这将失败,因为
thread::spawn()
期望'static
闭包(不从其环境借用的闭包),因为无法保证线程会在被借用的值被销毁之前终止。在您的代码中,这不应该发生,因为您在删除scene
之前耗尽了rx
,但类型系统不知道这一点。相反,可以考虑将
scene
Package 在Arc
中,这样可以为scene
提供线程安全的共享所有权,然后可以克隆Arc
句柄并为每个线程提供不同的句柄;每个句柄将引用相同的Scene
值。将以下内容作为函数的第一行插入:
然后在克隆
tx
时克隆Arc
:闭包中的
&scene
将自动引用Arc
,因此不需要更改它。另一种方法是使用crossbeam's scoped threads,它通过保证派生的线程必须在作用域被破坏之前终止来允许非静态借用。
如果您采用这种方法,请注意,您应该将
for
循环移到作用域中,以便它可以在派生线程发送消息时并行接收消息。如果您将它放在作用域之后,线程将发送所有消息,并在您开始处理消息之前终止,这意味着所有消息必须同时保存在内存中。顺便说一句,您的代码中有一个错误会导致死锁:从
rx
读取时tx
仍存在,因此从rx
阅读的循环永远不会终止,因为存在可用于发送更多邮件的打开的发件人。在for
循环之前添加drop(tx);
可关闭线程的本地发件人。