C ++ - 如何将文件分块以进行同步/异步处理?

时间:2012-11-20 23:46:45

标签: c++ multithreading asynchronous pthreads

如何按行数读取和分割/分块文件?

我想将文件分区到单独的缓冲区中,同时确保不在两个或多个缓冲区之间分割行。我计划将这些缓冲区传递给它们自己的pthreads,以便它们可以执行某种类型的同步/异步处理。

我已经阅读了reading and writing in chunks on linux using c下面的答案,但我认为它并不能确切地回答关于确保一行不会分成两个或更多缓冲区的问题。

3 个答案:

答案 0 :(得分:2)

文件是如何编码的?如果每个字节代表一个字符,我会执行以下操作:

  1. 内存使用mmap()映射文件。
  2. 通过根据适当的块大小计算作业来判断作业的大致开始和结束。
  3. 通过找到下一个'\n'
  4. 让每个工作找到实际的开始和结束
  5. 同时处理各个块。
  6. 请注意,第一个块需要特殊处理,因为它的开始不是近似的,而是精确的。

答案 1 :(得分:1)

我会选择以字节为单位的块大小。然后我会寻找文件中的适当位置,一次读取一些少量的字节,直到我有一个换行符。

第一个块的最后一个字符是换行符。第二个块的第一个字符是换行符后面的字符。

始终寻找pagesize()边界并一次读入pagesize()字节以搜索换行符。这将确保您只从磁盘中获取必要的最小值以找到您的边界。您可以尝试一次读取128个字节。但是,您可能会冒更多系统调用的风险。

我写了一个示例程序,用于字母频率计数。当然,这很容易分裂成线程,因为它几乎肯定是IO绑定的。并且新线的位置也无关紧要,因为它不是面向线的。但是,这只是一个例子。而且,它在很大程度上依赖于你有一个相当完整的C ++ 11实现。

他们的关键功能是:

// Find the offset of the next newline given a particular desired offset.
off_t next_linestart(int fd, off_t start)
{
   using ::std::size_t;
   using ::ssize_t;
   using ::pread;

   const size_t bufsize = 4096;
   char buf[bufsize];

   for (bool found = false; !found;) {
      const ssize_t result = pread(fd, buf, bufsize, start);
      if (result < 0) {
         throw ::std::system_error(errno, ::std::system_category(),
                                   "Read failure trying to find newline.");
      } else if (result == 0) {
         // End of file
         found = true;
      } else {
         const char * const nl_loc = ::std::find(buf, buf + result, '\n');
         if (nl_loc != (buf + result)) {
            start += ((nl_loc - buf) + 1);
            found = true;
         } else {
            start += result;
         }
      }
   }
   return start;
}

另请注意,我使用pread。当你有多个线程从文件的不同部分读取时,这是绝对必要的。

文件描述符是线程之间的共享资源。当一个线程使用普通函数从文件读取时,它会更改有关此共享资源(文件指针)的详细信息。文件指针是文件中下一次读取的位置。

在每次阅读之前简单地使用lseek对此没有帮助,因为它会在lseekread之间引入竞争条件。

pread函数允许您从文件中的特定位置读取一堆字节。它根本不会改变文件指针。除了它不会改变文件指针这一事实之外,它就像在同一个调用中组合lseekread一样。

答案 2 :(得分:0)

为缓冲区定义一个类。为每个人提供一个大的缓冲区空间,它是页面大小和开始/结束索引的一部分,一个从传入流中读取缓冲区的方法和一个将另一个*缓冲区实例作为参数的“lineParse”方法。 / p>

创建一些*缓冲区并将它们存储在生产者 - 使用者池队列中。打开文件,从池中获取缓冲区并从头到尾读入缓冲区空间(返回错误/ EOF的布尔值)。从池中获取另一个*缓冲区并将其传递给前一个的lineparse()。在那里,从数据末尾向后搜索,寻找newLine。找到后,重新加载结束索引并记忆最后一行的片段(如果有的话 - 你可能偶尔会幸运:),进入新的,传递的*缓冲区并设置其起始索引。第一个缓冲区现在有整行,可以排队到将处理这些行的线程。第二个缓冲区具有从第一个复制的行的片段,更多的数据可以从磁盘读取到其起始索引的缓冲区空间。

行处理线程可以将'used'*缓冲区回收到池中。

一直持续到EOF,(或错误:)。

如果可以,可以在缓冲区中添加一个方法来处理缓冲区。

使用大型缓冲区类并从最后解析将比继续读取小位更有效,从一开始就寻找换行符。线程间通信速度很慢,你可以传递的缓冲区越大越好。

使用缓冲池可以消除连续的新/删除并提供流控制 - 如果磁盘读取线程比处理速度快,则池将清空,磁盘读取线程将阻塞它,直到某些使用过的缓冲区被回收。这可以防止内存失控。

请注意,如果您使用多个处理线程,缓冲区可能会“无序”处理 - 这可能会或可能不会很重要。

您只能通过确保与磁盘读取延迟并行处理的行的优势大于线程间通信的开销来获得这种情况 - 在线程之间传递小缓冲区很可能会适得其反

网络磁盘总体来说速度最快,但延迟时间很长。