C语言管道读取始终返回0,进程间通信异常求助
多进程环形通信管道读取返回0问题排查与修复
我正在编写一个多进程环形通信程序,输入包含三个整数:
n:需要创建的进程数量c:初始值s:起始进程编号
程序逻辑
- 父进程创建所有子进程,将初始值
c发送给第s个进程 - 第
s个进程(特殊子进程)利用c生成一个大于c的秘密数,再将c发送给下一个子进程 - 每个子进程接收前一个进程传来的数值,加1后发送给下一个进程
- 当特殊子进程收到大于自身秘密数的整数时,将该结果发送给父进程,程序结束
当前问题
特殊子进程向父进程写入结果后,父进程的read操作始终返回0,无法读取到有效数据。
原始代码
#include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <time.h> #include "constants.h" #include <signal.h> int n; int generate_random_number(){ return (rand() % 50); } void close_all_pipes(int pipe_fd[][2], int i) { //Coses all pipes if i isn't in range, otherwise closes all the process won't use int prev = (i >= 0 && i < n) ? (i-1+n) % n : i; for(int j = 0; j < n; j++) { if (j != prev) { close(pipe_fd[prev][PIPE_WRITE]); } if (j != i) { close(pipe_fd[i][PIPE_READ]); } } } void hijo_favorito(int i, int buffer, int pipe_fd[][2]) { int numero_secreto = buffer - 1; do { numero_secreto = generate_random_number(); } while(numero_secreto <= buffer); close(pipe_fd[n][PIPE_READ]); close_all_pipes(pipe_fd, i); int num = buffer; write(pipe_fd[i][PIPE_WRITE], &num, sizeof(num)); while(1) { read(pipe_fd[(i-1 + n) % n][PIPE_READ], &num, sizeof(num)); if (num > numero_secreto) { printf("Num: %d\n", num); //for debugging purposes write(pipe_fd[n][PIPE_WRITE], &num, sizeof(num)); close(pipe_fd[n][PIPE_WRITE]); exit(EXIT_SUCCESS); } num++; write(pipe_fd[i % n][PIPE_WRITE], &num, sizeof(num)); } } void hijo_standard(int i, int pipe_fd[][2]) { close_all_pipes(pipe_fd, i); close(pipe_fd[n][PIPE_READ]); close(pipe_fd[n][PIPE_WRITE]); int num; while(1) { read(pipe_fd[(i-1 + n) % n][PIPE_READ], &num, sizeof(num)); num++; write(pipe_fd[i][PIPE_WRITE], &num, sizeof(num)); } } int main(int argc, char **argv) { //Funcion para cargar nueva semilla para el numero aleatorio srand(time(NULL)); int status, pid, start, buffer; n = atoi(argv[1]); buffer = atoi(argv[2]); start = atoi(argv[3]); if (argc != 4){ printf("Uso: anillo <n> <c> <s>\n"); exit(0);} if (start < 0 | start >= n) {printf("Proceso inicial inválido.\n"); exit(0);} printf("Se crearán %i procesos, se enviará el caracter %i desde proceso %i\n", n, buffer, start); pid_t *children = malloc(sizeof(*children) * n); int pipe_fd[n+1][2]; for(int i = 0; i < n+1; i++) pipe(pipe_fd[i]); close(pipe_fd[n][PIPE_WRITE]); for(int i = 0; i < n; i++) { if (i == start) { children[i] = fork(); if (children[i] < 0) exit(EXIT_FAILURE); else if (children[i] == 0) hijo_favorito(i, buffer, pipe_fd); } else { children[i] = fork(); if (children[i] < 0) exit(EXIT_FAILURE); else if (children[i] == 0) hijo_standard(i, pipe_fd); } } int res; sleep(1); //sync time read(pipe_fd[n][PIPE_READ], &res, sizeof(res)); printf("El número final es: %d\n", res); close(pipe_fd[n][PIPE_READ]); for(int i = 0; i < n; i++) {if (i != start) kill(children[i], SIGKILL);}; close_all_pipes(pipe_fd, n+1); free(children); }
问题根源分析
close_all_pipes函数逻辑错误:原函数误关闭了进程需要保留的管道端,导致环形通信中断,特殊子进程无法收到足够大的数值来触发向父进程的写入操作。- 父进程使用
sleep同步不可靠:sleep(1)无法保证特殊子进程完成数据写入,父进程可能提前执行read,此时管道写端未关闭但无数据,后续写操作完成后父进程也无法感知。 - 缺乏IO操作错误检查:未对
read/write的返回值做校验,无法及时发现管道通信异常。
修复方案
1. 重构close_all_pipes函数
确保每个进程只保留自身需要的管道端:
- 保留前一个进程管道的读端(用于接收数据)
- 保留自身管道的写端(用于发送数据)
- 关闭所有其他管道的两端
void close_all_pipes(int pipe_fd[][2], int i) { int prev = (i >= 0 && i < n) ? (i-1 + n) % n : -1; // 关闭所有不需要的管道端 for(int j = 0; j < n; j++) { // 保留前一个进程管道的读端,关闭其他所有读端 if (!(prev != -1 && j == prev)) { close(pipe_fd[j][PIPE_READ]); } // 保留自身管道的写端,关闭其他所有写端 if (!(i >= 0 && i < n && j == i)) { close(pipe_fd[j][PIPE_WRITE]); } } }
2. 替换sleep为可靠同步机制
使用waitpid等待特殊子进程退出,确保父进程在数据写入完成后再执行read操作。
3. 增加IO操作错误检查
对read/write的返回值进行校验,及时处理通信异常。
4. 增强程序健壮性
增加malloc、pipe、fork等系统调用的错误处理,避免程序意外崩溃。
修正后的完整代码
#include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <time.h> #include "constants.h" #include <signal.h> int n; int generate_random_number(){ return (rand() % 50); } void close_all_pipes(int pipe_fd[][2], int i) { int prev = (i >= 0 && i < n) ? (i-1 + n) % n : -1; // 关闭所有不需要的管道端 for(int j = 0; j < n; j++) { // 保留前一个进程管道的读端,关闭其他所有读端 if (!(prev != -1 && j == prev)) { close(pipe_fd[j][PIPE_READ]); } // 保留自身管道的写端,关闭其他所有写端 if (!(i >= 0 && i < n && j == i)) { close(pipe_fd[j][PIPE_WRITE]); } } } void hijo_favorito(int i, int buffer, int pipe_fd[][2]) { int numero_secreto = buffer - 1; do { numero_secreto = generate_random_number(); } while(numero_secreto <= buffer); // 关闭父子通信管道的读端(当前进程不需要读) close(pipe_fd[n][PIPE_READ]); // 关闭其他不需要的环形管道端 close_all_pipes(pipe_fd, i); int num = buffer; // 向环形管道发送初始值 write(pipe_fd[i][PIPE_WRITE], &num, sizeof(num)); while(1) { // 读取前一个进程的数据 ssize_t read_res = read(pipe_fd[(i-1 + n) % n][PIPE_READ], &num, sizeof(num)); if (read_res <= 0) { // 读取失败或管道关闭,退出 exit(EXIT_FAILURE); } if (num > numero_secreto) { printf("Num: %d\n", num); // 调试用 // 向父进程发送结果 write(pipe_fd[n][PIPE_WRITE], &num, sizeof(num)); close(pipe_fd[n][PIPE_WRITE]); exit(EXIT_SUCCESS); } num++; // 向后一个进程发送数据 write(pipe_fd[i][PIPE_WRITE], &num, sizeof(num)); } } void hijo_standard(int i, int pipe_fd[][2]) { // 关闭环形管道不需要的端 close_all_pipes(pipe_fd, i); // 关闭父子通信管道的两端(当前进程不需要) close(pipe_fd[n][PIPE_READ]); close(pipe_fd[n][PIPE_WRITE]); int num; while(1) { ssize_t read_res = read(pipe_fd[(i-1 + n) % n][PIPE_READ], &num, sizeof(num)); if (read_res <= 0) { exit(EXIT_FAILURE); } num++; write(pipe_fd[i][PIPE_WRITE], &num, sizeof(num)); } } int main(int argc, char **argv) { srand(time(NULL)); int start, buffer; if (argc != 4){ printf("Uso: anillo <n> <c> <s>\n"); exit(EXIT_FAILURE); } n = atoi(argv[1]); buffer = atoi(argv[2]); start = atoi(argv[3]); if (start < 0 || start >= n) { printf("Proceso inicial inválido.\n"); exit(EXIT_FAILURE); } printf("Se crearán %i procesos, se enviará el valor %i desde proceso %i\n", n, buffer, start); pid_t *children = malloc(sizeof(*children) * n); if (!children) { perror("malloc failed"); exit(EXIT_FAILURE); } int pipe_fd[n+1][2]; for(int i = 0; i < n+1; i++) { if (pipe(pipe_fd[i]) == -1) { perror("pipe failed"); exit(EXIT_FAILURE); } } // 父进程不需要写父子通信管道,关闭自己的写端 close(pipe_fd[n][PIPE_WRITE]); for(int i = 0; i < n; i++) { children[i] = fork(); if (children[i] < 0) { perror("fork failed"); exit(EXIT_FAILURE); } else if (children[i] == 0) { if (i == start) { hijo_favorito(i, buffer, pipe_fd); } else { hijo_standard(i, pipe_fd); } exit(EXIT_FAILURE); // 防止意外执行到这里 } } int res; // 等待特殊子进程退出,确保数据已写入 waitpid(children[start], NULL, 0); // 读取父进程管道的数据 ssize_t read_res = read(pipe_fd[n][PIPE_READ], &res, sizeof(res)); if (read_res == sizeof(res)) { printf("El número final es: %d\n", res); } else { printf("Error al leer el resultado\n"); } close(pipe_fd[n][PIPE_READ]); // 终止其他子进程并等待退出 for(int i = 0; i < n; i++) { if (i != start) { kill(children[i], SIGKILL); waitpid(children[i], NULL, 0); } } // 关闭剩余的管道端 close_all_pipes(pipe_fd, n+1); free(children); return EXIT_SUCCESS; }
内容的提问来源于stack exchange,提问作者Davidbowie123
相关产品推荐
相关产品推荐

