gpt4 book ai didi

multithreading - Rust:允许多个线程修改图像(向量的包装)?

转载 作者:行者123 更新时间:2023-12-03 11:26:56 25 4
gpt4 key购买 nike

假设我有一个包装矢量的“图像”结构:

type Color = [f64; 3];

pub struct RawImage
{
data: Vec<Color>,
width: u32,
height: u32,
}

impl RawImage
{
pub fn new(width: u32, height: u32) -> Self
{
Self {
data: vec![[0.0, 0.0, 0.0]; (width * height) as usize],
width: width,
height: height
}
}

fn xy2index(&self, x: u32, y: u32) -> usize
{
(y * self.width + x) as usize
}
}

它可以通过“ View ”结构访问,该结构抽象了图像的内部 block 。假设我只想写入图像 (set_pixel())。

pub struct RawImageView<'a>
{
img: &'a mut RawImage,
offset_x: u32,
offset_y: u32,
width: u32,
height: u32,
}

impl<'a> RawImageView<'a>
{
pub fn new(img: &'a mut RawImage, x0: u32, y0: u32, width: u32, height: u32) -> Self
{
Self{ img: img,
offset_x: x0, offset_y: y0,
width: width, height: height, }
}

pub fn set_pixel(&mut self, x: u32, y: u32, color: Color)
{
let index = self.img.xy2index(x + self.offset_x, y + self.offset_y);
self.img.data[index] = color;
}
}

现在假设我有一张图片,我希望有 2 个线程同时修改它。这里我使用了 rayon 的 scoped 线程池:

fn modify(img: &mut RawImageView)
{
// Do some heavy calculation and write to the image.
img.set_pixel(0, 0, [0.1, 0.2, 0.3]);
}

fn main()
{
let mut img = RawImage::new(20, 10);
let pool = rayon::ThreadPoolBuilder::new().num_threads(2).build().unwrap();
pool.scope(|s| {
let mut v1 = RawImageView::new(&mut img, 0, 0, 10, 10);
let mut v2 = RawImageView::new(&mut img, 10, 0, 10, 10);
s.spawn(|_| {
modify(&mut v1);
});
s.spawn(|_| {
modify(&mut v2);
});
});
}

这行不通,因为

  1. 我同时有2个&mut img,这是不允许的
  2. “闭包可能比当前函数长寿,但它借用了当前函数所拥有的v1

所以我的问题是

  1. 如何修改 RawImageView,以便有 2 个线程修改我的图像?
  2. 为什么它仍然提示闭包的生命周期,即使线程是作用域的?我该如何克服?

Playground link

我尝试过(并且有效)的一种方法是让 modify() 只创建并返回一个 RawImage,然后让线程将其插入一个向量。完成所有线程后,我从该矢量构建了完整图像。由于它的 RAM 使用,我试图避免这种方法。

最佳答案

你的两个问题实际上是不相关的。

首先是比较容易的#2:

Rayon 作用域线程的想法是,在作用域内创建的线程不能比作用域长,因此在作用域创建的任何变量都可以安全地借用,并将其引用发送到线程中。但是您的变量是在作用域内部创建的,这对您没有任何好处。

解决方案很简单:将变量移出作用域:

    let mut v1 = RawImageView::new(&mut img, 0, 0, 10, 10);
let mut v2 = RawImageView::new(&mut img, 10, 0, 10, 10);
pool.scope(|s| {
s.spawn(|_| {
modify(&mut v1);
});
s.spawn(|_| {
modify(&mut v2);
});
});

#1 比较棘手,你必须去不安全的地方(或者找一个可以为你做的 crate ,但我没有找到)。我的想法是存储原始指针而不是向量,然后使用 std::ptr::write 写入像素。如果您仔细操作并添加自己的边界检查,它应该是绝对安全的。

我将添加一个额外的间接级别,也许你可以只用两个级别来完成,但这将保留更多的原始代码。

RawImage 可能是这样的:

pub struct RawImage<'a>
{
_pd: PhantomData<&'a mut Color>,
data: *mut Color,
width: u32,
height: u32,
}
impl<'a> RawImage<'a>
{
pub fn new(data: &'a mut [Color], width: u32, height: u32) -> Self
{
Self {
_pd: PhantomData,
data: data.as_mut_ptr(),
width: width,
height: height
}
}
}

然后构建将像素保持在外部的图像:

    let mut pixels = vec![[0.0, 0.0, 0.0]; (20 * 10) as usize];
let mut img = RawImage::new(&mut pixels, 20, 10);

现在 RawImageView 可以保留对 RawImage 的不可变引用:

pub struct RawImageView<'a>
{
img: &'a RawImage<'a>,
offset_x: u32,
offset_y: u32,
width: u32,
height: u32,
}

并使用ptr::write 写入像素:

    pub fn set_pixel(&mut self, x: u32, y: u32, color: Color)
{
let index = self.img.xy2index(x + self.offset_x, y + self.offset_y);
//TODO! missing check bounds
unsafe { self.img.data.add(index).write(color) };
}

但不要忘记在此处检查边界或将此功能标记为不安全,将责任推给用户。

自然地,由于您的函数保留对可变指针的引用,因此它不能在线程之间发送。但我们更清楚:

unsafe impl Send for RawImageView<'_> {}

就是这样! Playground .我认为这个解决方案是内存安全的,只要您添加代码来强制您的 View 不重叠并且您不超出每个 View 的范围。

关于multithreading - Rust:允许多个线程修改图像(向量的包装)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64274964/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com