如何用父进程与两个子进程写入文件?循环fork引发重复写入问题求助
问题分析与修复方案
你猜的没错,核心问题就是循环内部重复调用fork()——每次循环都创建新的child_a和child_b,n=2时就会生成4个子进程,每个子进程都在执行写文件相关的操作,最终导致文件行数变成2ⁿ。而且原代码里子进程执行一次就exit(0),完全没有复用两个子进程来处理多组数据,这和需求里“仅使用两个子进程”的要求完全不符。
另外,原代码的同步逻辑(信号、管道读写)也存在很多时序问题,比如子进程发送信号后直接读写管道,没有确保对方已经准备好,很容易出现读写阻塞或数据错乱的情况。
关键修复点
1. 把fork()移到循环外
只创建一次child_a和child_b,让这两个子进程通过循环和父进程配合,完成N次数据处理,而不是每次处理都新建子进程。
2. 重构同步逻辑
用信号+管道的组合,严格按照需求的步骤来实现进程间的同步:
- 父进程启动后先等待
child_b的SIGUSR1信号 child_b发送信号后等待父进程写入数据到管道child_b读取数据后,通知child_a生成随机数child_a生成随机数写入管道,child_b读取后发给父进程- 父进程完成文件写入后,触发下一轮循环
3. 正确管理管道生命周期
确保管道的读写端在进程间正确共享,不要在单次操作后就关闭,直到所有数据处理完成后再关闭。
修复后的示例代码
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> #include <time.h> #include <string.h> // 全局原子变量用于信号同步(简单场景下可用,复杂场景建议用信号量) volatile sig_atomic_t parent_ready = 0; volatile sig_atomic_t child_b_ready = 0; volatile sig_atomic_t child_a_triggered = 0; void handler(int signum) { if (signum == SIGUSR1) { child_b_ready = 1; } else if (signum == SIGUSR2) { if (getpid() == getppid()) { // 父进程收到child_b的完成信号 parent_ready = 1; } else { // child_a收到触发信号 child_a_triggered = 1; } } } typedef struct { char address[100]; } Data; int main() { FILE *fp2 = fopen("bill.dat", "w"); // 用w清空文件,避免追加旧内容 if (!fp2) { perror("fopen failed"); exit(EXIT_FAILURE); } pid_t child_a, child_b; int pipefd_parent_to_b[2]; // 父进程->child_b的管道 int pipefd_a_to_b[2]; // child_a->child_b的管道 int pipefd_b_to_parent[2]; // child_b->父进程的管道 // 初始化信号处理 signal(SIGUSR1, handler); signal(SIGUSR2, handler); // 创建三个管道分别对应不同的数据流 if (pipe(pipefd_parent_to_b) == -1 || pipe(pipefd_a_to_b) == -1 || pipe(pipefd_b_to_parent) == -1) { perror("pipe failed"); exit(EXIT_FAILURE); } // 先创建child_a child_a = fork(); if (child_a < 0) { perror("fork child_a failed"); exit(EXIT_FAILURE); } if (child_a == 0) { // child_a逻辑:等待触发信号,生成随机数,写入管道 srand(time(NULL) ^ getpid()); // 用pid避免和父进程随机种子重复 close(pipefd_parent_to_b[0]); close(pipefd_parent_to_b[1]); close(pipefd_b_to_parent[0]); close(pipefd_b_to_parent[1]); while (1) { // 等待child_b的触发信号 while (!child_a_triggered) { pause(); } child_a_triggered = 0; // 生成1-5的随机数 int r = rand() % 5 + 1; char rand_str[10]; snprintf(rand_str, sizeof(rand_str), "%d", r); printf("Child_a generated random number: %s\n", rand_str); // 写入到child_b的管道 write(pipefd_a_to_b[1], rand_str, strlen(rand_str) + 1); } exit(EXIT_SUCCESS); } // 创建child_b child_b = fork(); if (child_b < 0) { perror("fork child_b failed"); exit(EXIT_FAILURE); } if (child_b == 0) { // child_b逻辑:按步骤和父进程、child_a交互 char data_buf[100]; char rand_buf[10]; close(pipefd_parent_to_b[1]); close(pipefd_a_to_b[0]); close(pipefd_b_to_parent[0]); while (1) { // 步骤2:向父进程发送信号,等待数据 kill(getppid(), SIGUSR1); printf("Child_b sent signal to parent, waiting for data...\n"); // 读取父进程发来的data[i] read(pipefd_parent_to_b[0], data_buf, sizeof(data_buf)); printf("Child_b read data from parent: %s\n", data_buf); // 步骤4:触发child_a生成随机数 kill(child_a, SIGUSR2); printf("Child_b triggered child_a...\n"); // 读取child_a的随机数 read(pipefd_a_to_b[0], rand_buf, sizeof(rand_buf)); printf("Child_b read random number from child_a: %s\n", rand_buf); // 步骤6:发送随机数给父进程 write(pipefd_b_to_parent[1], rand_buf, strlen(rand_buf) + 1); kill(getppid(), SIGUSR2); // 通知父进程可以读取了 } exit(EXIT_SUCCESS); } // 父进程逻辑:循环处理N组数据 Data data[] = {{"data1"}, {"data2"}}; // 示例数据,n=2 int n = sizeof(data) / sizeof(data[0]); close(pipefd_parent_to_b[0]); close(pipefd_a_to_b[0]); close(pipefd_a_to_b[1]); close(pipefd_b_to_parent[1]); for (int i = 0; i < n; i++) { parent_ready = 0; child_b_ready = 0; printf("\n============== Processing data %d ==============\n", i); // 步骤1:等待child_b的信号 printf("Parent waiting for child_b's signal...\n"); while (!child_b_ready) { pause(); } printf("Parent received signal from child_b\n"); // 步骤3:写入data[i]到管道 write(pipefd_parent_to_b[1], data[i].address, strlen(data[i].address) + 1); printf("Parent wrote data to pipe: %s\n", data[i].address); // 等待child_b发送随机数完成的信号 while (!parent_ready) { pause(); } // 读取child_b发来的随机数 char rand_buf[10]; read(pipefd_b_to_parent[0], rand_buf, sizeof(rand_buf)); // 步骤7:写入文件 fprintf(fp2, "%s - %s\n", data[i].address, rand_buf); printf("Parent wrote to file: %s - %s\n", data[i].address, rand_buf); } // 处理完成后终止子进程 kill(child_a, SIGKILL); kill(child_b, SIGKILL); waitpid(child_a, NULL, 0); waitpid(child_b, NULL, 0); fclose(fp2); return 0; }
代码说明
- 子进程复用:
child_a和child_b在循环外创建,内部用while(1)循环等待触发,直到父进程发送终止信号。 - 信号同步:用全局的原子变量跟踪信号状态,避免信号丢失或时序混乱。
- 管道分工:三个管道分别负责不同的数据流,避免数据交叉:
pipefd_parent_to_b:父进程向child_b发送数据pipefd_a_to_b:child_a向child_b发送随机数pipefd_b_to_parent:child_b向父进程发送随机数
- 资源清理:处理完成后父进程主动杀死子进程并等待回收,避免僵尸进程。
这样修改后,n=2时文件只会生成2行内容,完全符合需求。
内容的提问来源于stack exchange,提问作者TheBASS
相关产品推荐
相关产品推荐

