Multithreading 如何在Rust中的线程之间共享不可变数据?

Multithreading 如何在Rust中的线程之间共享不可变数据?,multithreading,rust,raytracing,Multithreading,Rust,Raytracing,我正在研究Rust中的光线跟踪器,作为学习该语言的一种方法,单线程版本运行良好。我想通过多线程加速它,在C/C++中多线程处理光线跟踪器相对容易,因为共享的大多数数据都是只读的(只有在写入像素数据时才会出现问题)。但我在生锈和它的所有权规则方面遇到了更多的麻烦 我有一个特点Hittable:Send+Sync用于世界上可能被击中的不同类型的东西(球体、网格),我将Send和Sync的实现留空,因为我实际上不需要它们中的任何一个。然后我有一个世界对象的向量,类型是vec。对于实际的多线程处理,我正

我正在研究Rust中的光线跟踪器,作为学习该语言的一种方法,单线程版本运行良好。我想通过多线程加速它,在C/C++中多线程处理光线跟踪器相对容易,因为共享的大多数数据都是只读的(只有在写入像素数据时才会出现问题)。但我在生锈和它的所有权规则方面遇到了更多的麻烦

我有一个特点
Hittable:Send+Sync
用于世界上可能被击中的不同类型的东西(球体、网格),我将
Send
Sync
的实现留空,因为我实际上不需要它们中的任何一个。然后我有一个世界对象的向量,类型是
vec
。对于实际的多线程处理,我正在尝试以下方法:

    let pixels_mutex: Arc<Mutex<Vec<Vec<(f64, f64, f64, u32)>>>> = Arc::new(Mutex::new(pixels));
    let vec_arc: Arc<Vec<Box<dyn Hittable>>> = Arc::new(vec);

    let mut thread_vec: Vec<thread::JoinHandle<()>> = Vec::new();

    for _ in 0..NUM_THREADS {
        let camera_clone = camera.clone();
        thread_vec.push(thread::spawn(move || {
            for r in 0..RAYS_PER_THREAD {
                if r % THREAD_UPDATE == 0 {
                    println!("Thread drawing ray {} of {} ({:.2}%)", r, RAYS_PER_THREAD, (r as f64 * 100.) / (RAYS_PER_THREAD as f64));
                }
                let u: f64 = util::rand();
                let v: f64 = util::rand();
                let ray = camera_clone.get_ray(u, v);
                let res = geometry::thread_safe_cast_ray(&ray, &vec_arc, MAX_DEPTH);
                let i = (u * IMAGE_WIDTH as f64).floor() as usize;
                let j = (v * IMAGE_HEIGHT as f64).floor() as usize;
                util::thread_safe_increment_color(&pixels_mutex, j, i, &res);
            }
        }));
    }

    for handle in thread_vec {
        handle.join().unwrap();
    }
let pixels_mutex:Arc=Arc::new(mutex::new(pixels));
设vec_弧:弧=弧::新(vec);
让mut-thread_-vec:vec=vec::new();
对于0..NUM_线程中的u{
让camera_clone=camera.clone();
线程向量推送(线程::生成(移动){
对于0中的r..RAYS\u PER\u线程{
如果r%THREAD\u UPDATE==0{
println!(({}({.2}%)的线程绘制光线{})”,r,每个线程的光线数,(r为f64*100。)/(每个线程的光线数为f64));
}
设u:f64=util::rand();
设v:f64=util::rand();
让光线=摄影机克隆。获取光线(u,v);
let res=几何体::螺纹、安全、投射、光线(光线、矢量、圆弧、最大深度);
设i=(u*图像\ u宽度为f64).floor()为usize;
设j=(v*图像高度为f64)。地板()为usize;
util::线程\安全\增量\颜色(&像素\互斥体、j、i和res);
}
}));
}
用于线程中的句柄{
handle.join().unwrap();
}
我已经实现了
thread\u safe\u increment\u color
,这似乎很好,但我会推迟执行
thread\u safe\u cast\u ray
,直到这个循环开始工作。我在这段代码中遇到的问题是,每个线程都试图将
vec_arc
移动到其闭包中,这违反了所有权规则。我试着复制
vec_arc
,就像我用
camera
做的一样,但编译器不让我这样做,我想这是因为我的
Hittable
特性不需要
Copy
clone
特性。而我的实现可命中的结构不能简单地派生(复制、克隆),因为它们包含一个
框,其中
材质是表示对象材质的另一个特征


我想这会容易得多,因为我知道大多数数据(除了
像素\u mutex
)都是只读的。如何在我正在创建的线程之间共享
vec\u arc
(就此而言,
pixels\u mutex
)。由于它是一个
Arc
,克隆它的成本很低,并且不需要
Vec
及其元素可克隆。您只需确保克隆智能指针而不是向量,并使用关联函数:

让vec_弧=弧::克隆(&vec_弧);
另一个选项是使用,在这种情况下,您根本不需要使用
Arc
,只需将闭包设置为非
move
,并直接访问局部变量即可。您的代码将如下所示:

    let pixels_mutex: Arc<Mutex<Vec<Vec<(f64, f64, f64, u32)>>>> = Arc::new(Mutex::new(pixels));
    let vec_arc: Arc<Vec<Box<dyn Hittable>>> = Arc::new(vec);

    let mut thread_vec: Vec<thread::JoinHandle<()>> = Vec::new();

    for _ in 0..NUM_THREADS {
        let camera_clone = camera.clone();
        thread_vec.push(thread::spawn(move || {
            for r in 0..RAYS_PER_THREAD {
                if r % THREAD_UPDATE == 0 {
                    println!("Thread drawing ray {} of {} ({:.2}%)", r, RAYS_PER_THREAD, (r as f64 * 100.) / (RAYS_PER_THREAD as f64));
                }
                let u: f64 = util::rand();
                let v: f64 = util::rand();
                let ray = camera_clone.get_ray(u, v);
                let res = geometry::thread_safe_cast_ray(&ray, &vec_arc, MAX_DEPTH);
                let i = (u * IMAGE_WIDTH as f64).floor() as usize;
                let j = (v * IMAGE_HEIGHT as f64).floor() as usize;
                util::thread_safe_increment_color(&pixels_mutex, j, i, &res);
            }
        }));
    }

    for handle in thread_vec {
        handle.join().unwrap();
    }
横梁::范围(| s |{
对于0..NUM_线程中的u{
让camera_clone=camera.clone();
线程向量推送(s.spawn(| | |{
//您的代码在这里,直接使用'vec'而不是'vec_弧'`
...
}).unwrap());
}
}).unwrap();//这里的线程是自动连接的

您应该能够像使用
相机一样克隆
vec\u arc
。但是您必须小心克隆指针而不是向量,因此应该在闭包之前使用
让vec_arc=arc::clone(&vec_arc)
。那能编译吗?那让
vec\u弧
工作起来了,谢谢!我现在发布了一个答案。您对
像素\u互斥体
有什么建议吗?我可以克隆它并将其移动到闭包中并返回每个克隆,但理想情况下,我希望所有线程都尝试写入同一个对象,并使用锁确保其中一个不会覆盖另一个。@CalvinGodfrey
pixels\u mutex
应以完全相同的方式处理:“克隆”
Arc
实际上并没有克隆任何东西,它只是增加了
Arc
中的引用计数;
的所有“克隆”都指向相同的底层对象。这同样适用于
vec_弧
后面的向量和
pixels_互斥体
后面的互斥体。哦,好吧!我没有意识到克隆人就是这么做的,这应该会让事情变得容易很多