如何正确并行执行多个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
问题列表
- 输出中的7和15来自哪里?我的文件中并没有这些数字。
- 我认为出现重复的有效数字是因为并行执行时每个
fscanf不知道当前的读取位置,导致多个fscanf读取同一行,这个假设是否正确? - 如何正确并行执行多个
fscanf? - 这么做是否有实际意义?
问题解答
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
相关产品推荐
相关产品推荐

