rust 值在循环的上一次迭代中移动到闭包中

2mbi3lxu  于 2023-01-17  发布在  其他
关注(0)|答案(1)|浏览(138)

我尝试在我实现的一个小型光线跟踪器中使用并行化。
我的想法是使用函数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()版本和上面的一样,没有txrxthread::spawn(),没有遇到任何问题,这让我更加困惑。
如何在多个同时闭包中使用scene变量(如果我正确理解了这个问题的话)?

qxsslcnc

qxsslcnc1#

scene被移到闭包中,因为您指定了move。要借用scene,您需要在派生线程之前将其重新绑定为引用,例如:

let tx_cell = tx.clone();
let scene = &scene;

这会将该作用域中的scene重新绑定为对外部scene的引用,然后将该引用移动到闭包中。
然而,这将失败,因为thread::spawn()期望'static闭包(不从其环境借用的闭包),因为无法保证线程会在被借用的值被销毁之前终止。在您的代码中,这不应该发生,因为您在删除scene之前耗尽了rx,但类型系统不知道这一点。
相反,可以考虑将scene Package 在Arc中,这样可以为scene提供线程安全的共享所有权,然后可以克隆Arc句柄并为每个线程提供不同的句柄;每个句柄将引用相同的Scene值。
将以下内容作为函数的第一行插入:

let scene = Arc::new(scene);

然后在克隆tx时克隆Arc

let tx_cell = tx.clone();
let scene = Arc::clone(scene);

闭包中的&scene将自动引用Arc,因此不需要更改它。
另一种方法是使用crossbeam's scoped threads,它通过保证派生的线程必须在作用域被破坏之前终止来允许非静态借用。
如果您采用这种方法,请注意,您应该将for循环移到作用域中,以便它可以在派生线程发送消息时并行接收消息。如果您将它放在作用域之后,线程将发送所有消息,并在您开始处理消息之前终止,这意味着所有消息必须同时保存在内存中。
顺便说一句,您的代码中有一个错误会导致死锁:从rx读取时tx仍存在,因此从rx阅读的循环永远不会终止,因为存在可用于发送更多邮件的打开的发件人。在for循环之前添加drop(tx);可关闭线程的本地发件人。

相关问题