使用fork()实现多子进程读取多文件并在父进程合并写入单文件的问题排查及优化方案咨询
使用fork()实现多子进程读取多文件并在父进程合并写入单文件的问题排查及优化方案咨询
嗨,我来帮你捋捋这段代码的问题,再聊聊更靠谱的实现思路~
首先得说,你现在代码的核心问题是没搞清楚fork之后父子进程的内存是完全独立的——子进程里读文件后修改的output数组、n这些变量,父进程根本拿不到!这就导致父进程写文件的时候,用的还是初始的空数据,自然出不来想要的结果。除此之外还有几个小坑:
- 子进程里的文件读取逻辑有问题:你先读了一遍统计数量,但没把文件指针移回开头,第二次
fscanf根本读不到内容; - 父进程每次循环都用
w+模式打开output.txt,这会清空之前的内容,就算能拿到数据,最后也只剩最后一次的写入; - 循环里的
wait位置不对,现在是每次fork后立刻等一个子进程,相当于串行处理,完全没用到多进程并行的优势,还会导致剩下的子进程变成僵尸进程。
那怎么改呢?最常用的方式是用管道来实现父子进程间的数据传递,因为管道能安全地让子进程把读取到的内容传给父进程。下面给你一个修正后的示例代码,逻辑更清晰:
#include <stdio.h> #include <sys/wait.h> #include <unistd.h> #include <stdlib.h> #define NUM_CHILDREN 3 int main() { int pipes[NUM_CHILDREN][2]; // 每个子进程对应一个管道,[0]是读端,[1]是写端 pid_t pids[NUM_CHILDREN]; // 先创建好所有管道 for (int k = 0; k < NUM_CHILDREN; k++) { if (pipe(pipes[k]) == -1) { perror("创建管道失败"); exit(EXIT_FAILURE); } } // 批量创建子进程 for (int k = 0; k < NUM_CHILDREN; k++) { pids[k] = fork(); if (pids[k] == 0) { // 子进程只负责读文件并通过管道传数据,先关闭不需要的读端 close(pipes[k][0]); char fn[100]; sprintf(fn, "sample%d.txt", (k + 1)); FILE *fptr = fopen(fn, "r"); if (!fptr) { perror("打开输入文件失败"); close(pipes[k][1]); exit(EXIT_FAILURE); } int num; // 读取文件里的每个整数,通过管道发给父进程 while (fscanf(fptr, "%d", &num) == 1) { write(pipes[k][1], &num, sizeof(int)); } // 读完关闭文件和管道写端,父进程会收到EOF信号 fclose(fptr); close(pipes[k][1]); exit(EXIT_SUCCESS); } else if (pids[k] == -1) { perror("创建子进程失败"); exit(EXIT_FAILURE); } } // 父进程这边,先关闭所有管道的写端(只需要读) for (int k = 0; k < NUM_CHILDREN; k++) { close(pipes[k][1]); } // 打开输出文件准备写入 FILE *fp = fopen("output.txt", "w"); if (!fp) { perror("打开输出文件失败"); exit(EXIT_FAILURE); } // 按顺序从每个管道读取数据,写入输出文件 for (int k = 0; k < NUM_CHILDREN; k++) { int num; ssize_t bytes_read; while ((bytes_read = read(pipes[k][0], &num, sizeof(int))) > 0) { fprintf(fp, "%d ", num); } close(pipes[k][0]); } fclose(fp); // 等待所有子进程结束,避免僵尸进程 for (int k = 0; k < NUM_CHILDREN; k++) { waitpid(pids[k], NULL, 0); } printf("文件合并完成!\n"); return 0; }
另外还有几个可以优化的方向,你可以根据需求选:
- 如果文件是文本内容,也可以直接传递字符串(注意缓冲区大小),不用逐个传整数,处理起来更灵活;
- 如果需要更高性能的进程间通信,可以考虑共享内存,但对于这个场景来说,管道已经足够简单易用,没必要复杂化;
- 可以增强错误处理逻辑,比如对每个系统调用的返回值都做检查,避免忽略潜在的错误。
备注:内容来源于stack exchange,提问作者cereyanci23
相关产品推荐
相关产品推荐

