为多线程应用程序使用多个文件流读取同一文件

Reading the same file using multiple file streams for multithread applications

提问人:danula godagama 提问时间:9/25/2023 更新时间:9/25/2023 访问量:63

问:

问题背景:我有一个大型二进制文件,其中包含具有独特结构的数据。这些数据的一个单位称为“事件”。每个事件有 32016 个字节,单个文件包含大约 400000 个事件,使文件大小为 ~12 GB。我正在编写一个程序来处理事件,并尝试使用多线程方法,其中多个线程读取文件的不同段(让每个线程使用自己的文件流)。

问题是 fseek 无法找到文件的正确位置。以下是最小可重现的示例。该程序读取一个包含473797事件的二进制文件,计划使用 20 个胎面,而每个胎面使用不同的文件流。

#include <iostream>
#include <stream>
#include <errno.h>
#include <string.h>

using namespace std;

int main(){

        FILE *segment[20];
        int ret=0;
        int eventsPerThread=473797/20;
        int eventSize=8004;
        for(int k=0;k<20;++k){
                segment[k]=fopen("Datafile_367.bin","rb");
                if(segment[k]==NULL){
                        std::cout<<"file stream is NULL!"<<k<<"\n";

                }

                ret=fseek(segment[k],eventsPerThread*eventSize*4*k,SEEK_SET);
                std::cout<<ret<<":::"<<strerror(errno)<<"\n";


        }
                return 0;
}

以下是输出。fseek 有时成功,有时返回 0,而在其他时候失败,错误代码为 22(无效参数)。

0:::Success
0:::Success
0:::Success
-1:::Invalid argument
-1:::Invalid argument
-1:::Invalid argument
0:::Invalid argument
0:::Invalid argument
0:::Invalid argument
-1:::Invalid argument
-1:::Invalid argument
-1:::Invalid argument
0:::Invalid argument
0:::Invalid argument
0:::Invalid argument
-1:::Invalid argument
-1:::Invalid argument
0:::Invalid argument
0:::Invalid argument
0:::Invalid argument

对 fseek() 函数的这种行为有什么解释吗?

(请注意,最小可重现的例子是单胎面,一旦程序开始读取事件,就会发生多线程)

c fopen fseek

评论

2赞 erik258 9/25/2023
我建议改变你的方法。在一个线程中读取所有事件,然后将它们调度到工作线程。从磁盘读取数据将受到 I/O 限制;存储设备处理顺序访问的能力远远优于随机访问。因此,在一个线程中读取文件实际上可能比在 20 个线程中同时读取文件更快。
1赞 Craig Estey 9/25/2023
用于映射文件,并具有所有线程使用的单个映射mmap
0赞 danula godagama 9/25/2023
@erik258 由于文件的大小,该方法不适合此应用程序。我更受事件处理而不是数据读取的限制,特别是因为数据文件存储在 RAID SSD 驱动器中。与处理单个事件所需的时间相比,从磁盘读取单个事件的时间可以忽略不计。
1赞 linuxfan says Reinstate Monica 9/25/2023
@erik258是对的。您不能加快读取速度,但可以使用更多的 CPU 来加快处理速度。你说的正是埃里克所说的。单个线程读取事件可最大化 I/O,然后将每个事件卸载到线程池中。这完全适合您的应用。
0赞 danula godagama 9/25/2023
@linuxfansaysReinstateMonica我知道他说了什么。我知道我不能加快阅读速度,我不想加快阅读速度,我不在乎阅读速度。阅读比处理快得多。这不是一个小文件,我不能只读取所有事件并将它们存储在内存中,只是为了让工作线程访问。我不想实现复杂的线程锁,以免损害实际的处理能力。一个阅读胎面比几个胎面快吗?可能,勉强。它对这个应用程序的最终输出有好处吗?不。

答:

3赞 the busybee 9/25/2023 #1

错误是偏移量计算中的溢出。您使用 ,它显然是 4 个字节宽。 对于此宽度2147483647。intINT_MAX

我看看:

k eventsPerThread * eventSize * 4 * k 溢出int 返回值fseek()
0 0 0 0
1 758427024 758427024 0
2 1516854048 1516854048 0
3 2275281072 -2019686224 -1
4 3033708096 -1261259200 -1
5 3792135120 -502832176 -1
6 4550562144 255594848 0
7 5308989168 1014021872 0
: : : :

由于溢出,结果变得消极,并且对此不满意。intfseek()

首先,确保 s 的宽度超过 4 个字节。然后将乘法的至少一个操作数更改为 。例如像这样.longlongeventsPerThread * eventSize * 4L * k

最后说明:考虑使用更多空格来使代码更具可读性。

评论

1赞 nielsen 9/25/2023
如果代码依赖于超过 4 个字节宽的 longs,那么最好将该假设构建到代码本身中。例如:._Static_assert(sizeof(long)>4, "Long is not so long");