【问题标题】:Child process is blocked by full pipe, cannot read in parent process子进程被全管道阻塞,无法在父进程中读取
【发布时间】:2011-12-08 09:30:42
【问题描述】:

我大致创建了以下代码来调用子进程:

// pipe meanings
const int READ = 0;
const int WRITE = 1;

int fd[2];
// Create pipes
if (pipe(fd))
  {
    throw ...
  }
p_pid = fork();
if (p_pid == 0) // in the child
  {
    close(fd[READ]);
    if (dup2(fd[WRITE], fileno(stdout)) == -1)
      {
        throw ...
      }
    close(fd[WRITE]);

    // Call exec
    execv(argv[0], const_cast<char*const*>(&argv[0]));
    _exit(-1);
  }
else if (p_pid < 0) // fork has failed
  {
    throw
  }
else // in th parent
  {
      close(fd[WRITE]);
      p_stdout = new std::ifstream(fd[READ]));
  }

现在,如果子进程没有向标准输出写入太多内容,我可以等待它完成,然后从p_stdout 读取标准输出。如果它写入太多,则写入阻塞并且父级永远等待它。 为了解决这个问题,我尝试在父级中使用 WNOHANG 等待,如果尚未完成,请使用 readsome 从 p_stdout 读取所有可用输出,稍等片刻,然后重试。不幸的是,readsome 从来没有读过任何东西:

while (true)
  {
    if (waitid(P_PID, p_pid, &info, WEXITED | WNOHANG) != 0)
      throw ...;
    else if (info.si_pid != 0) // waiting has succeeded
      break;

    char tmp[1024];
    size_t sizeRead;
    sizeRead = p_stdout->readsome(tmp, 1024);
    if (sizeRead > 0)
      s_stdout.write(tmp, sizeRead);
    sleep(1);
  }

问题是:为什么这不起作用,我该如何解决?

编辑:如果只有孩子,简单地使用read 而不是readsome 可能会起作用,但是该进程有多个孩子并且需要在其中一个终止时立即做出反应。

【问题讨论】:

    标签: c++ fork parent-child pipe


    【解决方案1】:

    正如 sarnold 所建议的,您需要更改通话顺序。先读,最后等。即使您的方法有效,您也可能会错过最后一次阅读。即在读取最后一组写入的字节之前退出循环。

    问题可能是 ifstream 是非阻塞的。我从不喜欢 iostream,即使在我的 C++ 项目中,我也一直喜欢 C 的 stdio 函数(即 FILE*、fprintf 等)的简单性。解决这个问题的一种方法是读取描述符是否可读。您可以使用 select 来确定该管道上是否有数据等待。无论如何,如果您要从多个孩子那里阅读,您将需要选择,所以不妨现在学习它。

    至于快速易读的功能,试试这样的(请注意我没有尝试编译这个):

    bool isreadable(int fd, int timeoutSecs)
    {
        struct timeval tv = { timeoutSecs, 0 };
        fd_set readSet;
        FD_ZERO(&readSet);
        return select(fds, &readSet, NULL, NULL, &tv) == 1;
    }
    

    然后在您的父代码中,执行以下操作:

    while (true) {
        if (isreadable(fd[READ], 1)) {
            // read fd[READ];
            if (bytes <= 0)
                break;
        }
    }
    
    wait(pid);
    

    【讨论】:

      【解决方案2】:

      我建议重新编写代码,以便它不会调用waitpid(2),直到之后 read(2) 调用管道返回0 以表示文件结束。一旦你从你的读取调用中得到文件结束返回,你就知道孩子已经死了,你终于可以waitpid(2) 了。

      另一种选择是进一步将读取与收获分离,并在 SIGCHLD 信号处理程序中与读取操作异步执行等待调用。

      【讨论】:

      • 对不起,我的问题不准确。我需要使用readsome 而不是read,因为该进程有多个子进程并且使用read 会阻塞,直到输出可用。
      • 关于阅读部分,是的,你是对的,我应该之前应该这样做。现在,在我知道子进程已终止后,我尝试读取其余的输出,也许我应该以不同的方式排序。不过,它会有所作为吗?不幸的是,我不明白你所说的 SIGCHLD 处理程序是什么意思。你能给我一个解释的链接吗?
      • 对于“也许我应该以不同的方式订购”——如果您的子程序打算写更多的 PIPE_BUF 字节(我的 pipe(7) 手册页中提到了 4096 字节),那么它们将停止直到他们管道中的数据已被消耗。在你读到足够多的内容让它们填满剩余的内核缓冲区并继续写入之前,它们不会死掉。
      • 对于信号处理程序,您需要通过sigaction(2)SIGCHLD 信号安装一个函数。每当您的孩子死亡时,内核将安排您的函数运行。您可以在处理程序中执行的操作非常有限——为此,请参阅signal(7) 联机帮助页(或the OpenGroup docs)。通常,您只需在处理程序中 wait(2) 并记下程序其余部分的状态以供以后使用。
      • 哦,一个想法:readsome 似乎没有工作,但您没有检查流的错误状态。 cplusplus has a guide on that,但像往常一样,cplusplus.com 无论如何都不是确定的,因此请使用您为ios 错误位获得的任何更好的文档。我越来越睡眼惺忪了,所以我该睡觉了。使用错误检查的结果编辑问题,以便取代我的其他人更容易看到它。 :)
      猜你喜欢
      • 1970-01-01
      • 2016-06-05
      • 1970-01-01
      • 1970-01-01
      • 2015-07-03
      • 1970-01-01
      • 2013-07-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多