为什么父进程的输出被子进程阻塞?

Why is output of parent process blocked by child process?

在我下面的代码中,我将我的进程分叉为父进程和子进程。在子进程中,我将c字符串argv[1]发送给父进程进行打印。然后我让子进程在打印 "This is the child process. Closing\n" 之前休眠 4 秒。

在父进程中,我希望从子进程接收到的字符串立即打印到标准输出。

问题就出在这里。而不是在 4 秒后打印字符串 "This is the child process. Closing\n" 之前立即在父进程中打印 argv[1],会发生这样的事情:

$ g++ -std=c++11 printchild.cpp -o printchild

$ ./printchild helloworld

1) 4 秒过去了

2) "This is the child process. Closing\n" 被打印

3) "helloworld" 被打印

为什么父进程的输出被子进程阻塞了?

// printchild.cpp
#include <chrono>
#include <thread>
#include <cstdio>
#include <unistd.h>
#include <cstdlib>

int main(int argc, char * argv[]){
  int pipefd[2];
  pid_t cpid;
  if(argc != 2){
    fprintf(stderr, "Usage: %s <string>\n", argv[0]);
    exit(EXIT_FAILURE);
  }
  if(pipe(pipefd) == -1){
    perror("fork");
    exit(EXIT_FAILURE);
  }
  cpid = fork();
  if(cpid == 0){
    close(pipefd[0]);
    FILE *fpc = fdopen(pipefd[1],"wb");
    fprintf(fpc,"%s",argv[1]);
    fflush(fpc);
    std::this_thread::sleep_for(std::chrono::seconds(4));
    fprintf(stdout,"This is the child process. Closing\n");
    return 0;
  }else if(cpid == -1){
    perror("Error in forking");
    exit(EXIT_FAILURE);
  }else{
    char str[80];
    close(pipefd[1]);
    FILE* fp = fdopen(pipefd[0], "rb");
    fgets(str,80,fp);
    fprintf(stdout,"%s\n",str);
    return 0;
  }
}

如果您在刷新后立即关闭客户端中的文件,它将按预期工作:

fflush(fpc);
fclose(fpc);

输出:

[dbush] /tmp/x1 hello
hello
[dbush] This is the child process. Closing

parent 进程正在通过 fgets() 读取 child 的消息。它将继续阅读,直到发生以下三种情况之一:

  • 已读取足够的字节来填充缓冲区,少了一个字符串终止符
  • 读取换行符
  • end-of-file遇到

child 没有发送足够的字节来耗尽缓冲区,也没有发送换行符,所以 parent 的 fgets() 没有 return直到 child 的管道末端在其出口处关闭。

您可以在 child 中解决此问题,方法是让它以换行符终止消息或在写入后立即关闭流。

您的问题是 fgets 电话。 它试图完全满足您的请求,这就是阻塞的原因。

如果您正在制作一些 IPC 的原型,我建议您放弃 stdio 并使用原始的 IO 系统调用。

如果您替换,例如:

char buf[100];
read(pipefd[0], buf, 100);
fprintf(stdout,"%s\n",buf);

对于 fgets 部分,您会立即收到消息,因为 read 会为您提供可用的任何内容,而 return(如果管道中至少有 1 个字节在您发出 read 请求时缓冲),而 fgets 不会后退,直到它读取了所有请求的字节或直到它发现它不能(EOF 或 IO 错误) .

(说实话,阻塞的不是 fgets(仅用户空间代码无法阻塞)——这是 fgets 发出的另一个暂时无法满足的 read 系统调用)。