如何为 Rust 中的不同线程克隆随机数生成器?

How can I clone a Random Number Generator for different threads in Rust?

我正在研究基于 C++ 代码库(PBRT,如果有人熟悉它)的光线追踪器的 Rust 实现。 C++ 版本定义的 类 之一是一系列采样器,以减少渲染图像中的噪声。在渲染过程中,只要需要随机数,这个采样器就会被克隆到每个渲染线程中。这就是我在 Rust 中选择的方式,我承认这有点令人费解:

#[derive(Clone)]
pub struct PixelSampler {
    samples_1d: Vec<Vec<f64>>,
    samples_2d: Vec<Vec<Point2<f64>>>,
    current_1d_dimension: i32,
    current_2d_dimension: i32,
    rng: rngs::ThreadRng
}
pub enum Samplers {
    StratifiedSampler {x_samples: i64, y_samples: i64, jitter_samples: bool, pixel: PixelSampler },
    ZeroTwoSequenceSampler { pixel: PixelSampler }
}

impl Clone for Samplers {
    fn clone(&self) -> Self {
        match self {
            Samplers::StratifiedSampler { x_samples, y_samples, jitter_samples, pixel } => { 
                Samplers::StratifiedSampler {x_samples: *x_samples,
                                             y_samples: *y_samples,
                                             jitter_samples: *jitter_samples,
                                             pixel: pixel.clone() }
             }
            Samplers::ZeroTwoSequenceSampler { pixel } => { Samplers::ZeroTwoSequenceSampler{ pixel: pixel.clone() } }
        }
    }
}

然后我还有一个 Integrator,它有一个 Samplers 变体字段。在我的主渲染循环中,我为每个线程运行了以下循环:

for _ in 0..NUM_THREADS {
    let int_clone = integrator.clone(); // integrator just contains a Samplers
    thread_vec.push(thread::spawn(move || {
        loop {
            // do main rendering loop
        }
    }));
}

但是当我用这个编译时,我得到了错误:

“特性 std::marker::Send 没有为 std::ptr::NonNull<rand::rngs::adapter::reseeding::ReseedingRng<rand_chacha::chacha::ChaCha20Core, rand_core::os::OsRng>> 实现”。

我的理解是,因为我只是将克隆版本移动到线程中,所以没有必要实现 Send。我做错了什么?

正如 thread_rng() 文档所说,它是:

[...]essentially just a reference to the PRNG in thread-local memory.

因此,通过克隆“rng”,您并没有复制生成器及其状态(我假设这是您的意图),而是为线程本地 RNG 创建了一个新句柄。此句柄有意不是 Send,因为它访问线程局部 RNG 而无需锁定以提高效率。

如果您希望您的结构包含一个实际的 RNG,并克隆该结构以复制它,您可以使用 StdRng type, which is the recommended, efficient and secure RNG. To instantiate it, use the methods in the SeedableRng 特性。例如:

#[derive(Clone)]
pub struct PixelSampler {
    samples_1d: Vec<Vec<f64>>,
    samples_2d: Vec<Vec<Point2<f64>>>,
    current_1d_dimension: i32,
    current_2d_dimension: i32,
    rng: StdRng,
}
// ...

let sampler = Samplers::ZeroTwoSequenceSampler {
    pixel: PixelSampler {
        samples_1d: vec![],
        samples_2d: vec![],
        current_1d_dimension: 0,
        current_2d_dimension: 0,
        rng: SeedableRng::from_entropy(),
    },
};
// compiles because StdRng is Send
std::thread::spawn(move || sampler).join().unwrap();

playground 中的完整示例。