You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确并行执行多个fscanf?并行文件读取问题排查

并行调用fscanf读取文件的问题与解决

问题背景

我尝试通过并行执行多个fscanf来提升文件读取速度,文件10-nums.txt包含10个数字,内容如下:

478
205
256
175
196
52
594
333
72
777

初始代码与异常输出

编写的初始代码main.c:

#include <stdio.h>

int main(void) {
    FILE *file = fopen("10-nums.txt", "r");
    int x;

    #pragma omp parallel for
    for (int j = 0; j < 10; ++j) {
        fscanf(file, "%d", &x);
        printf(" %d", x);
    }
}

编译命令:gcc.exe "path/to/c/file" -std=c2x -O3 -fopenmp -o some_name
得到的输出:

7 7 7 205 256 256 256 256 15 7

更新后的代码与输出

更新后的main.c:

#include <stdio.h>
#include <stdlib.h>
#include <omp.h>

int main(void) {
    FILE *file = fopen("nums-10.txt", "r");

    if (file == NULL) {
        printf("File not found\n");
    }

    #pragma omp parallel for
    for (int j = 0; j < 10; ++j) {
        int x;
        int y = fscanf(file, "%d", &x);
        printf("thread-num = %d | x = %d | y = %d\n", omp_get_thread_num(), x, y);
    }

    fclose(file);

    return EXIT_SUCCESS;
}

输出结果:

thread-num = 9 | x = 48 | y = 1
thread-num = 4 | x = 6 | y = 1
thread-num = 8 | x = 758 | y = 1
thread-num = 7 | x = 4 | y = 1
thread-num = 6 | x = 167 | y = 1
thread-num = 0 | x = 33 | y = 1
thread-num = 5 | x = 178 | y = 1
thread-num = 1 | x = 32765 | y = 0
thread-num = 3 | x = 6 | y = 1
thread-num = 2 | x = 72 | y = 1

问题列表

  1. 输出中的7和15来自哪里?我的文件中并没有这些数字。
  2. 我认为出现重复的有效数字是因为并行执行时每个fscanf不知道当前的读取位置,导致多个fscanf读取同一行,这个假设是否正确?
  3. 如何正确并行执行多个fscanf?
  4. 这么做是否有实际意义?

问题解答

1. 异常数字的来源

这些无意义的数字是数据竞争导致的未定义行为:

  • 多个线程同时操作同一个FILE指针,文件的内部读写偏移量被无序修改,fscanf读取到被打乱的字节序列,解析出错误整数。
  • 初始代码中x是线程共享变量,多个线程同时写入x,导致输出值被覆盖、混乱,出现随机垃圾值。

2. 关于重复数字的假设

这个假设部分正确:

  • 核心问题是FILE对象并非线程安全,多线程同时调用fscanf时,文件偏移量的操作完全无序,可能出现多个线程从同一位置读取,或读取过程中偏移量被其他线程修改,导致重复读取同一内容。
  • 同时,初始代码中共享变量x的写入竞争,也会导致某个线程读取的数值被覆盖前被打印,出现重复的有效数字。

3. 正确并行读取文件的方式

直接并行调用fscanf不可行,需避免多线程直接操作同一文件流,推荐两种方案:

方案1:预分割文件,线程读取独立块

先获取文件总大小,将文件分割为多个连续块,每个线程单独打开文件并定位到对应块的起始位置,读取自己的块内容(需注意处理块边界的整数完整性,避免跨块整数被截断):

#include <stdio.h>
#include <omp.h>
#include <sys/stat.h>

int main() {
    struct stat st;
    stat("10-nums.txt", &st);
    long file_size = st.st_size;
    int num_threads = omp_get_max_threads();
    long block_size = file_size / num_threads;

    #pragma omp parallel
    {
        int tid = omp_get_thread_num();
        FILE *file = fopen("10-nums.txt", "r");
        // 定位到块起始位置
        fseek(file, tid * block_size, SEEK_SET);
        
        // 读取块内容(需额外处理边界整数)
        int x;
        while (fscanf(file, "%d", &x) == 1) {
            printf("thread %d: %d\n", tid, x);
        }
        fclose(file);
    }
    return 0;
}

方案2:主线程读数据,线程并行处理

如果目标是并行处理数据,更简单的方式是主线程先一次性将所有数据读取到内存数组,再用并行线程处理数组内容,完全避免文件IO竞争:

#include <stdio.h>
#include <omp.h>

int main() {
    FILE *file = fopen("10-nums.txt", "r");
    int nums[10];
    // 主线程读取所有数据
    for (int i = 0; i < 10; ++i) {
        fscanf(file, "%d", &nums[i]);
    }
    fclose(file);

    // 并行处理数据
    #pragma omp parallel for
    for (int i = 0; i < 10; ++i) {
        printf("thread %d: %d\n", omp_get_thread_num(), nums[i]);
    }
    return 0;
}

4. 并行读取文件的实际意义

  • 小文件场景:完全无意义,线程创建、调度的开销远大于文件IO开销,甚至会因竞争导致性能下降。
  • 大文件场景:仅当文件存储在支持并发读取的设备(如SSD、分布式存储)上时,分割块并行读取可能小幅提升效率,但收益有限——文件IO的瓶颈通常是硬件带宽,而非CPU处理能力。大部分情况下,主线程顺序读取+线程并行处理数据的模式性价比更高。

内容的提问来源于stack exchange,提问作者EzioMercer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 06:25:14