我正在尝试使用 C 语言的多线程读取文件来读取文件,但是当我根据文件大小划分块时,有些块可能会在行的中间开始/结束。我试图调整块大小以防发生这种情况。这些线的大小不一样。我正在尝试读取大约 100 MB 的非常大的文件。
首先,这是一个好方法还是应该将多线程仅用于其他任务,例如处理线路?
我的方法是移动块的开头和结尾,直到找到一行的结尾。这样,如果一个块恰好位于一行内(当线程数 > 行数时),开始和结束将匹配,我将不会启动该线程。大多数时候它读取得很好,但有时,一行没有被读取,或者一行被同一个线程读取两次,或者一行的一小部分被另一个线程读取。
不同线程同时读取同一个文件有什么问题吗?
long chunk_size = file_size / num_threads;
pthread_t threads[num_threads];
ThreadData thread_data[num_threads];
long last_end = 0;
for (uint32_t i = 0; i < num_threads; ++i)
{
thread_data[i].stats = stats;
thread_data[i].thread_tweets = NULL;
thread_data[i].failed = 0;
thread_data[i].file = file;
thread_data[i].start = i * chunk_size;
thread_data[i].end = (i == num_threads - 1) ? file_size : (i + 1) * chunk_size;
if (i > 0)
{
if (thread_data[i].end < thread_data[i - 1].start)
{
thread_data[i].failed = 1;
continue;
}
}
int ch;
// Adjust start position to the beginning of the next line
if (!is_start_at_line_boundary(file, thread_data[i].start))
{
fseek(file, thread_data[i].start, SEEK_SET);
while ((ch = fgetc(file)) != '\n' && ch != EOF);
thread_data[i].start = ftell(file);
}
// Adjust end position to the end of the line
fseek(file, thread_data[i].end, SEEK_SET);
while ((ch = fgetc(file)) != '\n' && ch != EOF);
thread_data[i].end = ftell(file);
if (ch != '\n' && ch != EOF)
{
thread_data[i].end++;
}
// If they coincide, the chunk was inside a line and the thread shoudnt run
if (thread_data[i].end == thread_data[i].start)
{
thread_data[i].failed = 1;
continue;
}
if (i > 0)
{
thread_data[i].start = last_end;
}
if (pthread_create(&threads[i], NULL, read_file_chunk, &thread_data[i]))
{
fprintf(stderr, "Error creating thread\n");
exit(EXIT_FAILURE);
}
last_end = thread_data[i].end;
}
int is_start_at_line_boundary(FILE *file, long start)
{
if (start == 0)
{
return 1; // Start of the file
}
fseek(file, start - 1, SEEK_SET);
if (fgetc(file) == '\n')
{
return 1; // Start is at the beginning of a line
}
return 0;
}
函数 read_file_chunk 将使用
fseek()
转到块的开头,并使用 fgets()
读取整个块,其中它为每一行调用解析函数,因为每行包含一个单独的 json 格式,而不是 ,
有 ;
,例如:
{"created_at": "2020-01-14 12:00:00"; "hashtags": ["A", "B"]; "id": 546542; "uid": 1500}
我应该使用 json 库而不是假设每一行都是一个 json 对象吗?这样会更高效、更明智吗?
我正在尝试读取大约 100 MB 的非常大的文件。首先,这是一个好方法还是应该将多线程仅用于其他任务,例如处理线路?
非常粗略地说,典型的硬盘读取速度为 150 MB/s。对于 SSD,可能为 400 MB/s。 许多因素会影响实际吞吐量,但我认为可以安全地假设 100 MB 文件的单线程顺序读取时间不应超过 1 秒。 如果文件是“热”文件(由文件系统或操作系统缓存),速度可能会快得多。
HDD 和 SSD 都是顺序设备,因此即使有多个线程尝试同时读取文件的不同块,每个线程也可能必须等待其他线程的读取。
在我的实验中,如果您需要将整个文件读入内存,最简单也是最快的方法:对整个文件发出一次读取。
(如果您希望可能不需要将整个文件放入内存和/或需要乱序读取文件,则将文件映射到内存可能是一个好主意。这两种情况似乎都不适合您的情况。)
谨防过早优化。 从一个有效的单线程实现开始。
如果速度不够快,那么你必须进行测量以确定瓶颈在哪里。 在您的情况下,这可能是将文件读入内存所需的时间,也可能是 JSON 解析的时间。
如果读取文件是瓶颈,我会寻找多线程以外的解决方案。
如果解析 JSON 是瓶颈,那么多线程可能是值得探索的一种可能的解决方案。 在这种情况下,我最初会保留单线程 I/O 不变,并将多线程应用于数据解析。
(我并不否认在某些情况下你可能想将阅读内容分成几个部分,但我不会从那里开始。)