You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言管道读取始终返回0,进程间通信异常求助

多进程环形通信管道读取返回0问题排查与修复

我正在编写一个多进程环形通信程序,输入包含三个整数:

  • n:需要创建的进程数量
  • c:初始值
  • s:起始进程编号

程序逻辑

  1. 父进程创建所有子进程,将初始值c发送给第s个进程
  2. 第s个进程(特殊子进程)利用c生成一个大于c的秘密数,再将c发送给下一个子进程
  3. 每个子进程接收前一个进程传来的数值,加1后发送给下一个进程
  4. 当特殊子进程收到大于自身秘密数的整数时,将该结果发送给父进程,程序结束

当前问题

特殊子进程向父进程写入结果后,父进程的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);
}

问题根源分析

  1. close_all_pipes函数逻辑错误:原函数误关闭了进程需要保留的管道端,导致环形通信中断,特殊子进程无法收到足够大的数值来触发向父进程的写入操作。
  2. 父进程使用sleep同步不可靠:sleep(1)无法保证特殊子进程完成数据写入,父进程可能提前执行read,此时管道写端未关闭但无数据,后续写操作完成后父进程也无法感知。
  3. 缺乏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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 13:25:54