如何在 Rust 中的线程之间共享不可变数据?
How can I share immutable data between threads in Rust?
我正在研究 Rust 中的光线追踪器,作为学习该语言的一种方式,单线程版本工作正常。我想通过多线程来加速它,并且在 C/C++ 中对光线跟踪器进行多线程处理相对容易,因为大部分共享数据都是只读的(唯一的问题发生在写入像素数据时)。但是我在 Rust 及其所有权规则方面遇到了很多麻烦。
我有一个特点 Hittable: Send + Sync
可以处理世界上可能被击中的不同类型的事物(球体、网格),我将实现留给了 Send
和 Sync
空白,因为我实际上不需要它们中的任何一个。然后我有一个类型为 Vec<Box<dyn Hittable>>
的世界对象的 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();
}
我已经实现了 thread_safe_increment_color
,这看起来不错,但我会推迟 thread_safe_cast_ray
,直到我让这个循环工作。我对这段代码感到 运行 的问题是每个线程都试图将 vec_arc
移动到它的闭包中,这违反了所有权规则。我尝试像使用 camera
一样制作 vec_arc
的克隆,但编译器不允许我这样做,我认为这是因为我的 Hittable
特性不需要 [=21] =] 或 Clone
特征。我的实现 Hittable
的结构不能简单地 derive(Copy, Clone)
因为它们包含一个 Box<dyn Material>
,其中 Material
是表示对象 material 的另一个特征。
我认为这会更容易,因为我知道大部分数据(pixels_mutex
除外)都是只读的。如何在我创建的线程之间共享 vec_arc
(就此而言,pixels_mutex
)?
您应该像使用相机一样克隆 vec_arc
。因为它是一个 Arc
,克隆它很便宜并且不需要 Vec
及其元素是可克隆的。您只需要确保克隆的是智能指针而不是向量,并使用相关函数:
let vec_arc = Arc::clone(&vec_arc);
另一种选择是使用 scoped threads,在这种情况下您根本不需要使用 Arc
,您可以简单地使闭包成为非 move
并访问直接局部变量。您的代码将如下所示:
crossbeam::scope(|s| {
for _ in 0..NUM_THREADS {
let camera_clone = camera.clone();
thread_vec.push(s.spawn(|_| {
// your code here, using `vec` directly instead of `vec_arc`
...
}).unwrap());
}
}).unwrap(); // here threads are joined automatically
我正在研究 Rust 中的光线追踪器,作为学习该语言的一种方式,单线程版本工作正常。我想通过多线程来加速它,并且在 C/C++ 中对光线跟踪器进行多线程处理相对容易,因为大部分共享数据都是只读的(唯一的问题发生在写入像素数据时)。但是我在 Rust 及其所有权规则方面遇到了很多麻烦。
我有一个特点 Hittable: Send + Sync
可以处理世界上可能被击中的不同类型的事物(球体、网格),我将实现留给了 Send
和 Sync
空白,因为我实际上不需要它们中的任何一个。然后我有一个类型为 Vec<Box<dyn Hittable>>
的世界对象的 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();
}
我已经实现了 thread_safe_increment_color
,这看起来不错,但我会推迟 thread_safe_cast_ray
,直到我让这个循环工作。我对这段代码感到 运行 的问题是每个线程都试图将 vec_arc
移动到它的闭包中,这违反了所有权规则。我尝试像使用 camera
一样制作 vec_arc
的克隆,但编译器不允许我这样做,我认为这是因为我的 Hittable
特性不需要 [=21] =] 或 Clone
特征。我的实现 Hittable
的结构不能简单地 derive(Copy, Clone)
因为它们包含一个 Box<dyn Material>
,其中 Material
是表示对象 material 的另一个特征。
我认为这会更容易,因为我知道大部分数据(pixels_mutex
除外)都是只读的。如何在我创建的线程之间共享 vec_arc
(就此而言,pixels_mutex
)?
您应该像使用相机一样克隆 vec_arc
。因为它是一个 Arc
,克隆它很便宜并且不需要 Vec
及其元素是可克隆的。您只需要确保克隆的是智能指针而不是向量,并使用相关函数:
let vec_arc = Arc::clone(&vec_arc);
另一种选择是使用 scoped threads,在这种情况下您根本不需要使用 Arc
,您可以简单地使闭包成为非 move
并访问直接局部变量。您的代码将如下所示:
crossbeam::scope(|s| {
for _ in 0..NUM_THREADS {
let camera_clone = camera.clone();
thread_vec.push(s.spawn(|_| {
// your code here, using `vec` directly instead of `vec_arc`
...
}).unwrap());
}
}).unwrap(); // here threads are joined automatically