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

使用select复用匿名管道与其他文件描述符时的无限读取问题

解决select监听匿名管道时无限触发读取事件的问题

这个问题我之前排查过好几次,核心是你没处理管道读端遇到EOF的场景,咱们来一步步理清楚问题出在哪,以及怎么修复:

问题根源分析

当子进程写完管道数据并关闭写端后,父进程的管道读端(pfd[0])在读完所有数据后,后续的read()调用会直接返回0(这是管道写端关闭的标志,也就是EOF)。但你的代码里既没有判断这个返回值,也没有把pfd[0]从master文件描述符集合中移除,导致select()会一直认为这个fd是可读的,进而无限触发读取逻辑,形成死循环。

另外还有个小问题:你把close(pfd[1]);放在了每次管道读取的分支里,这完全没必要——父进程从一开始就不需要写管道,应该在fork之后立刻关闭父进程的写端。

修正后的代码

#include <iostream>
#include <stdlib.h>
#include <algorithm>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
using namespace std;

int main(){
    fd_set master; // master file descriptor list
    fd_set read_fds; // temp file descriptor list for select()
    FD_ZERO(&master); // clear the master and temp sets
    FD_ZERO(&read_fds);
    int fdmax; // maximum file descriptor number
    int pfd[2];

    if(pipe(pfd)!=0) {
        cout<<"Unable to create a pipe.\n";
        exit(1);
    };

    FD_SET(0, &master);
    FD_SET(pfd[0],&master);
    fdmax = max(pfd[0], 0); // 确保fdmax是当前最大的fd值

    pid_t pid = fork();
    if(pid > 0){ // Parent
        // 父进程不需要写管道,立刻关闭写端
        close(pfd[1]);

        for (;;){
            read_fds = master; // 复制master集合到临时集合
            if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
                perror("select");
                exit(4);
            }

            for(int i = 0; i <= fdmax; i++) {
                if (FD_ISSET(i, &read_fds)) {
                    int n;
                    char buff[200];
                    if (i == pfd[0]){
                        n = read(pfd[0], buff, sizeof(buff)-1); // 留一个位置给字符串终止符
                        if (n == 0) {
                            // 管道写端已关闭,移除该fd并关闭读端
                            FD_CLR(pfd[0], &master);
                            close(pfd[0]);
                            // 如果当前关闭的是最大fd,更新fdmax
                            if (pfd[0] == fdmax) {
                                fdmax = -1;
                                for(int j=0; j <= pfd[0]; j++){
                                    if(FD_ISSET(j, &master) && j > fdmax){
                                        fdmax = j;
                                    }
                                }
                            }
                            cout<<"Pipe closed, stop listening to it\n";
                            continue;
                        } else if (n < 0) {
                            perror("read pipe");
                            FD_CLR(pfd[0], &master);
                            close(pfd[0]);
                            continue;
                        }
                        buff[n] = 0;
                        cout<<"Read from pipe:"<<buff<<endl;
                    } else if(i == 0){
                        n = read(0, buff, sizeof(buff)-1);
                        if (n == 0) {
                            // stdin关闭,移除并关闭fd
                            FD_CLR(0, &master);
                            close(0);
                            if (0 == fdmax) {
                                fdmax = -1;
                                for(int j=0; j <= 0; j++){
                                    if(FD_ISSET(j, &master) && j > fdmax){
                                        fdmax = j;
                                    }
                                }
                            }
                            cout<<"Stdin closed\n";
                            continue;
                        } else if (n < 0) {
                            perror("read stdin");
                            FD_CLR(0, &master);
                            close(0);
                            continue;
                        }
                        buff[n] = 0;
                        cout<<"Read from std:"<<buff<<endl;
                    }
                }
            }
            // 没有需要监听的fd时退出循环
            if (fdmax == -1) {
                cout<<"No more fds to listen, exit\n";
                break;
            }
        }
    } else if(pid == 0){ // Child
        usleep(50000);
        char buff[200] = "This is a simple sample.";
        close(pfd[0]); // 子进程不需要读管道,关闭读端
        write(pfd[1], buff, strlen(buff)); // 只写入有效字符串内容,避免多余空字符
        close(pfd[1]);
        exit(0);
    } else {
        perror("fork");
        exit(2);
    }
    return 0;
}

关键修改点说明

  • 父进程fork后立刻关闭管道写端pfd[1],避免资源泄漏
  • 严格处理read()的返回值:
    • 当n == 0时,说明对应fd的写端已关闭,将该fd从master集合中移除并关闭,同时更新fdmax(如果该fd是当前最大值)
    • 当n < 0时,处理读错误场景,同样移除并关闭fd
  • 子进程写管道时,用strlen(buff)代替sizeof(buff),避免写入数组中多余的空字符
  • 增加fdmax的动态更新逻辑,确保select()的参数始终正确
  • 当没有需要监听的fd时主动退出循环,避免无意义的空转

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 04:02:47