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

Linux下C语言阻塞管道生产者消费者进程的结束信号问题

解决方案:利用管道的EOF特性实现结束信号

首先,你当前的问题根源有两个:

  1. 用null字符作为结束标志不适合多生产者/消费者场景——多个生产者会写入多个null,消费者读到第一个就停止,导致剩余数据未被读取;而消费者多于生产者时,部分消费者永远等不到null。
  2. 文件描述符传递错误:你通过execl传递&n(int指针),但execl需要字符串参数,这会导致生产者/消费者拿到的文件描述符完全错误,这可能是程序异常的隐藏原因。

核心思路:利用管道的EOF机制

Unix管道的特性是:当所有写端文件描述符都被关闭时,read()会返回0,表示到达文件末尾(EOF)。我们可以利用这个特性作为消费者的结束信号:

  • 所有生产者写完数据后,关闭自己的管道写端。
  • 当最后一个生产者关闭写端后,管道的所有写端都关闭,此时消费者调用read()会返回0,即可停止读取并退出。

具体代码修改步骤

1. 修正主程序的文件描述符传递逻辑

原来的execl传递&n是错误的,需要将文件描述符转为字符串传递:

// 主程序中创建生产者的部分修改
case 0:
    if((n = dup(pipeT[1])) == -1) exit(5);
    close(pipeT[0]);
    char prod_fd[10];
    sprintf(prod_fd, "%d", n); // 将文件描述符转为字符串
    if(execl("./producer", "producer", prod_fd, NULL) == -1) exit(4);
    break;

// 创建消费者的部分修改
case 0:
    if((n = dup(pipeT[0])) == -1) exit(5);
    close(pipeT[1]);
    char cons_fd[10];
    sprintf(cons_fd, "%d", n);
    if(execl("./consumer", "consumer", cons_fd, NULL) == -1) exit(4);
    break;

2. 修改生产者代码:移除null字符,写完后关闭写端

int main(int argc, char *argv[]) {
    srand(getpid());
    int n = atoi(argv[1]); // 修正:将字符串转为整数文件描述符
    char sign;
    char nameOfFile[20];
    sprintf(nameOfFile, "P/p_%d", getpid());
    FILE* f = fopen(nameOfFile, "w");
    if(f == NULL) exit(1);

    int numberOfSigns = rand()%100;
    for(int i = 0; i < numberOfSigns; i++) {
        sign = rand()%94 + 32;
        if (write(n, &sign, sizeof(char)) == -1) exit(EXIT_FAILURE);
        fprintf(f, "%c", sign);
    }

    // 移除写null字符的代码
    fclose(f);
    close(n); // 写完后关闭自己的管道写端
    return 0;
}

3. 修改消费者代码:通过read返回值判断结束

int main(int argc, char *argv[]) {
    int n = atoi(argv[1]); // 修正:将字符串转为整数文件描述符
    char sign;
    char nameOfFile[20];
    sprintf(nameOfFile, "K/k_%d", getpid());
    FILE* f = fopen(nameOfFile, "w");
    if(f == NULL) exit(1);

    // 当read返回>0时读取数据,返回0表示EOF,返回-1表示错误
    ssize_t read_bytes;
    while((read_bytes = read(n, &sign, sizeof(char))) > 0) {
        fprintf(f, "%c", sign);
    }
    if(read_bytes == -1) {
        exit(EXIT_FAILURE);
    }

    fclose(f);
    close(n); // 关闭管道读端
    return 0;
}

为什么这个方案可行?

  • 当所有生产者都写完数据并关闭写端后,管道的所有写端都被关闭,此时消费者的read()会返回0,触发循环结束,不会提前终止。
  • 无论生产者和消费者数量如何匹配,消费者都会读取到管道中的所有数据,直到EOF才停止,确保数据不丢失。
  • 修正了文件描述符传递的错误,保证管道读写的正确性。

额外注意事项

  • 确保P/和K/目录存在,否则生产者/消费者创建文件会失败,可以在主程序开头添加mkdir("P", 0755); mkdir("K", 0755);(需要包含sys/stat.h和sys/types.h)。
  • 主程序中关闭pipeT[0]和pipeT[1]的时机是对的:创建完所有子进程后,主程序不再需要管道的两端,关闭它们不会影响子进程的dup后的fd。

内容的提问来源于stack exchange,提问作者Zar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:05:47