子进程被完整管道阻塞,无法在父进程中读取

时间:2011-12-08 09:30:43

标签: c++ fork parent-child pipe

我粗略地创建了以下代码来调用子进程:

// 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]));
  }

现在,如果子进程没有对stdout写太多,我可以等待它完成,然后从p_stdout读取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可能会有效,但该过程有多个孩子,需要尽快做出反应他们终止了。

2 个答案:

答案 0 :(得分:3)

正如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);

答案 1 :(得分:1)

我建议重新编写代码,以便在管道返回waitpid(2)read(2)之后的之前不会调用0来表示档案结尾。一旦你从你的阅读电话中获得文件结束返回,你就知道孩子已经死了,你终于可以waitpid(2)了。

另一个选择是将读数与收割进一步解耦,并在SIGCHLD信号处理程序中与读取操作异步执行等待调用。