c++如何正确实现并行if流读取

问题描述 投票:0回答:1

首先,我想做的事情。我有一个类,可以处理存储上的一种特殊类型的大文件。我所说的处理,基本上是指以下几个方面。

class Large {
  std::ifstream file;
  Large(const char* fname) : file(fname) { 
    // read file header, create an index of stuff that's inside, that sort of stuff
    ... 
  }

  void read_a_part(int which_part) const {
    // read from ifstream, which unfortunately modifies its state
    fstream.read(...)
  }

  ...
}

现在,这都是伪代码,因为我对最佳实践感兴趣 而不是单一的解决方案。函数 read_a_state 将不会被编译,因为它修改了对象状态,但却被声明为 const.

在我试图实现的目标中,我并没有修改我的File(我只是从它那里读取),也没有修改Large的实例。因此,所有的函数都应该被声明为 const 并应重入并行工作。看来,这不能用单一的 ifstream 身为 Large. 不标记文件访问成员函数的解决方案 Large 作为 const 或使该成员 file 可变的都不好。这样的解决方案是不复存在的。由于 ifstream 也不能复制,似乎每次调用到 read_a_part. 因此,我看到的唯一可行的解决方案是移动 file 的内部变量,从成员变成了 read_a_part 以及该类的大多数其他成员函数)。但是从性能上来说,这是个好的解决方案吗?成千上万次对ifstream ctor的调用不会给操作系统带来问题吗?就没有更好的方法吗?

c++ parallel-processing const ifstream file-read
1个回答
0
投票

把所有的都做成const而不做成const,这个逻辑稍微有点奇怪。

例如,编译器可能会错过读操作的重复调用(它会认为结果不会改变)。

但是,你可以在类中存储指针。

  • 你可以把文件变量的指针存储到类中 然后在构造函数中初始化它(const指针);
  • 你可以在文件中存储句柄(fopen函数的结果)。并通过构造函数进行初始化。

哼哼。祝您好运。


0
投票

磁盘在顺序访问上表现最好(即使是SSD)。因此,并行化磁盘IO很可能会导致整体吞吐量的降低。

说到这里,有两种可能的解决方案。

  1. 按顺序读取文件,然后将部分文件交给线程池进一步处理。

  2. 使用mutex将文件访问序列化。

    class Large {
      std::ifstream file;
      mutable std::mutex mtx;
    public:
      Large(const char* fname) : file(fname) { 
        // read file header, create an index of stuff that's inside, that sort of stuff
        ... 
      }
    
      Part read_a_part(int which_part) const {
        Part part;
        std::lock_guard<std::mutex> lock(mtx);
        file.seekg(which_part * part_size);
        file.read(...&part...);
        return part;
      }
    
    }
    

不知道为什么你的 read_a_part() 必须 const. 如果没有,那么你可以直接删除 const 并去除 mutable 从mutex的。


0
投票

我是在回答自己的问题,因为答案是否定的,几乎不存在一个 完美 的解决方案。所以,为了供大家参考,我列举了几种权衡解决方案,我已经考虑过了。

  • 内存映射文件。这在C或C++标准中是不支持的,但在POSIX中是支持的。它可以在所有主要的操作系统上使用,没有太大的问题,但是要通过第三方的lib。
  • 接受所有的文件访问都有一个状态。即使只进行读取,状态也不是const,文件读取函数默认是不重入的。因此必须采取线程安全措施。
  • 使用扔掉的对象。每次访问文件.都会获得一个新的句柄(打开一个新的ifstream),读取文件的一部分,然后丢弃句柄。包裹读取部分的函数是重入式的,不保留状态。不需要外部依赖。

我选择了所以使用最后一种解决方案。这不是最高效的方案,但却是最简单的方案。

© www.soinside.com 2019 - 2024. All rights reserved.