rust 保持输入到输出的顺序,而每个输出来自一个应该读取一次的文件

5cnsuln7  于 2023-11-19  发布在  其他
关注(0)|答案(1)|浏览(106)

我正在努力想出最好的方法来构建我的程序,关于一个特定的功能。

以下是一些细节:

  • 函数的输入是一个(纬度,经度)坐标向量
  • 这些可以是 * 无论用户提供的顺序 *
  • 函数的输出应为输入坐标处的高程,顺序与输入相同
  • 通过访问TIFF文件中的像素值来检索高程,TIFF文件由整数纬度,经度坐标命名,例如n40w106n40w107,.。
  • 此数字表示左上角,例如:
  • (39.5,-105.5),(39.8,-105.2)都是n40w106.tif
  • (40.3,-106.1),(40.5,-106.9)都是n41w107.tif
  • 这意味着可能需要从同一文件中检索输入向量中不同点的多个点
  • 我已经有了所有的功能,以采取输入点和检索海拔从相应的文件

我正在努力解决的是我想确保的这些额外要求:

  • 每个文件应该只加载一次
  • 一次只能有一个文件在内存中(所以我不认为缓存会起作用)
  • 输出的顺序应与提供的输入的顺序相对应
    CS中是否有一些现有的范例或概念可以实现我想要的?

我还想知道是否有一种模型可以很好地处理并发。作为参考,我使用Rust编程语言。

mwngjboj

mwngjboj1#

每个文件只应该被加载一次,并且它们只应该在使用时保存在内存中,这意味着你必须提前确定一组都来自同一个文件的组,否则你不能同时满足这两个约束。
为了保持顺序,可以提前创建输出缓冲区,然后,当将输入列表组织成组时,可以将其索引包含在输入列表中,以便处理该项的代码可以将其直接写入输出缓冲区中的正确槽中。
最后,您将每个组发送到一个线程,在该线程中可以一次性处理单个文件的所有条目。
请注意,由于Rust的别名规则,线程通常不能在没有某种互斥保护的情况下同时写入缓冲区,即使它们写入不同的索引。但是,您可以通过在输出缓冲区中使用split_first_mut来解决这个问题,将可变引用存储到应该存储输出值的位置。这样就没有别名,* 和 * 不需要互斥。
您还需要使用scoped threads,以便将这些可变切片发送到线程。
最后,返回输出缓冲区。
下面是一个函数的例子,它接受一个数字对(a, b)的列表,其中处理一个数字对的结果是它们的乘积,但是具有相同b值的所有数字对应该一起处理:

use std::collections::HashMap;

fn process_groups(items: &[(i32, i32)]) -> Vec<i32> {
    let mut output = vec![0; items.len()];
    let mut output_head = output.as_mut_slice();

    let mut groups: HashMap<i32, Vec<(i32, &mut i32)>> = HashMap::new();

    for &(a, b) in items {
        let (output_pos, tail) = output_head.split_first_mut().unwrap();
        output_head = tail;

        groups.entry(b).or_default().push((a, output_pos));
    }

    std::thread::scope(|s| {
        for (b, items) in groups {
            s.spawn(move || {
                for (a, output) in items {
                    *output = a * b;
                }
            });
        }
    });

    output
}

#[test]
fn example() {
    assert_eq!(
        process_groups(&[(2, 4), (16, 3), (7, 4), (9, 4), (25, 2), (18, 3),]),
        vec![2 * 4, 16 * 3, 7 * 4, 9 * 4, 25 * 2, 18 * 3]
    );
}

字符串
Playground
这里有几个需要注意的警告:

  • 由于你的代码需要从文件中读取,所以在你的例子中处理函数是容易出错的。你需要一种方法来将错误传递回调用者。你可以让任何错误导致整个操作失败(返回Result<Vec<_>, _>),或者你可以通过允许个别结果失败来返回尽可能多的结果(返回Vec<Result<_, _>>)。
  • 示例代码假设您可以构造一个默认值来初始填充输出缓冲区。如果不存在这样的值,您可能需要使用None作为占位符,并在线程中使用Some值填充它。或者,有一个涉及不安全代码和MaybeUninit的过程,在该过程中,您可以为项分配所需的空间,而无需使用Option * 或 * 提前初始化它们,但是这样做的代码很难正确,并且代码中的任何错误都可能导致可靠性问题。
  • 示例代码为每个组创建一个线程。创建固定数量的线程并使用channel将作业排队到这些线程可能会更有效。

相关问题