使用C语言fork多进程统计文件总行数结果异常的解决方法
多进程统计文件总行数的问题与解决方法
当前代码通过fork创建多进程统计文件行数,但最终统计的总行数与实际不符,同时子进程输出的行数累加后和显示的总行数差异极大,核心问题有两点:
- 所有子进程都将文件指针重置到开头,导致每个子进程都重复统计了整个文件的行数;
- 使用
exit(lines)传递行数时,WEXITSTATUS只能获取退出状态的低8位(范围0-255),当行数超过255时会被截断,导致累加结果错误。
原代码
char *filename = "/home/septian/Documents/Repository.cs"; FILE *file = fopen(filename, "r"); if (file == NULL) { perror("File open failed"); return 1; } int num_processes = 4; // Number of child processes to create pid_t pid[num_processes]; for (int i = 0; i < num_processes; i++) { pid_t child_pid = fork(); if (child_pid < 0) { perror("Fork failed"); return 1; } else if (child_pid == 0) { // This code runs in the child process int lines = 0; char ch; fseek(file, 0, SEEK_SET); // Rewind the file to the beginning // Read the file and count lines while ((ch = fgetc(file)) != EOF) { if (ch == '\n') { lines++; } } printf("Child process %d counted %d lines\n", i, lines); exit(lines); // Terminate the child process with the line count } else { pid[i] = child_pid; // Store child process IDs in an array } } // This code runs in the parent process printf("parent process\n"); int total_lines = 0; for (int i = 0; i < num_processes; i++) { int status; waitpid(pid[i], &status, 0); // Wait for child processes to finish total_lines += WEXITSTATUS(status); } printf("Total lines in the file: %d\n", total_lines); fclose(file);
错误输出
parent process
Child process 1 counted 15340 lines
Child process 3 counted 8866 lines
Child process 2 counted 5361 lines
Child process 0 counted 7089 lines
Total lines in the file: 816
正确实现思路与代码
核心改进点
- 分割文件区间:先获取文件总大小,将文件划分为多个连续区间,每个子进程负责统计一个区间内的行数,避免重复统计;
- 安全传递结果:使用管道(pipe)在父子进程间传递统计结果,替代
exit传递,避免数值截断问题; - 处理边界情况:解决换行符跨区间、文件最后一行无换行符的统计遗漏问题。
修改后的代码
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <sys/stat.h> int main() { char *filename = "/home/septian/Documents/Repository.cs"; FILE *file = fopen(filename, "r"); if (file == NULL) { perror("File open failed"); return 1; } // 获取文件大小 struct stat file_stat; if (fstat(fileno(file), &file_stat) == -1) { perror("Failed to get file stats"); fclose(file); return 1; } off_t file_size = file_stat.st_size; int num_processes = 4; off_t chunk_size = file_size / num_processes; int pipes[num_processes][2]; // 每个子进程对应一个管道 for (int i = 0; i < num_processes; i++) { // 创建管道 if (pipe(pipes[i]) == -1) { perror("Pipe creation failed"); fclose(file); return 1; } pid_t child_pid = fork(); if (child_pid < 0) { perror("Fork failed"); fclose(file); return 1; } else if (child_pid == 0) { // 子进程:关闭读端,只写 close(pipes[i][0]); off_t start = i * chunk_size; off_t end = (i == num_processes - 1) ? file_size : (i + 1) * chunk_size; // 定位到区间起始位置 if (fseek(file, start, SEEK_SET) == -1) { perror("Fseek failed in child"); exit(1); } int lines = 0; char ch; // 处理区间起始前的换行:如果前一个字符是换行,当前区间需要计数 if (start > 0) { fseek(file, start - 1, SEEK_SET); ch = fgetc(file); if (ch == '\n') { lines++; } fseek(file, start, SEEK_SET); } // 统计当前区间内的换行 while (ftell(file) < end && (ch = fgetc(file)) != EOF) { if (ch == '\n') { lines++; } } // 处理文件最后一行(无换行符的情况) if (i == num_processes - 1 && ch == EOF) { if (file_size > 0) { fseek(file, -1, SEEK_END); ch = fgetc(file); if (ch != '\n') { lines++; } } } printf("Child process %d counted %d lines\n", i, lines); // 通过管道发送结果 write(pipes[i][1], &lines, sizeof(lines)); close(pipes[i][1]); fclose(file); exit(0); } else { // 父进程:关闭写端,只读 close(pipes[i][1]); } } // 父进程汇总结果 printf("parent process\n"); int total_lines = 0; for (int i = 0; i < num_processes; i++) { int lines; read(pipes[i][0], &lines, sizeof(lines)); total_lines += lines; close(pipes[i][0]); // 等待子进程结束 wait(NULL); } printf("Total lines in the file: %d\n", total_lines); fclose(file); return 0; }
代码说明
- 文件分割:根据进程数将文件划分为均等的块,最后一个进程负责剩余的所有内容,避免重复统计;
- 管道通信:每个子进程通过管道将统计的行数发送给父进程,摆脱了
exit状态的数值范围限制; - 边界处理:检查区间起始位置的前一个字符是否为换行,避免漏统计;同时处理文件最后一行无换行符的情况,确保总行数准确。
内容的提问来源于stack exchange,提问作者Septian Ibn Yohan
相关产品推荐
相关产品推荐

