【问题标题】:read from a pipe is blocked after close writer关闭写入器后从管道读取被阻塞
【发布时间】:2017-01-04 20:44:16
【问题描述】:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char **argv) {

    int childs[3];
    for (int i = 0; i < 3; ++i) {
        int p[2];
        if (pipe(p) == -1) { perror("pipe"); exit(1); }

        pid_t pid = fork();
        if (pid) {
            close(p[0]);
            childs[i] = p[1];
        }
        else {
            close(p[1]);
            printf("child %d start\n", i + 1);

            char buf[10];
            buf[0] = 0;
            int r;
            if ((r = read(p[0], buf, 9)) == -1) { ... }

            printf("child %d read %s (%d), finish\n", i + 1, buf, r);

            sleep(2);
            exit(0);
        }
    }

    for (int i = 0; i < 3; ++i) {
    //        if (argc > 1) {
    //            write(childs[i], "42", 2);
    //        }
    // ============== HERE >>>
        close(childs[i]);
    }

    pid_t pid;
    while ((pid = waitpid(-1, NULL, 0)) > 0) {
         printf("child %d exited\n", pid);
    }

    return 0;
}

带有注释的输出:

child 1 start
child 2 start
child 3 start
child 3 read  (0), finish

2秒后显示下一行

child 2 read  (0), finish

2秒后显示下一行

child 1 read  (0), finish

我不写到父频道。关闭它,我想向将在read 中等待的孩子发出信号。

好像有以下的。 Сhild N 预期完成读取结果 0,没关系。孩子2 (N-1)1 被锁定在一个read 到一个孩子3 完成。那么1的孩子类似会等。

为什么会发生锁?

【问题讨论】:

    标签: c unix ipc system-calls


    【解决方案1】:

    子进程从其父进程继承打开的文件描述符。你的主进程循环打开文件描述符(使用pipe,只保留写结束)。 Child 1 不继承任何描述符(stdin/stdout/stderr 除外);孩子 2 继承 childs[0] (去孩子 1 的描述符);孩子 3 继承了 childs[0]childs[1](描述符转到孩子 1 和 2)。

    read 在管道上阻塞只要任何写描述符仍然打开(因为它可以用来发送更多数据)。所以孩子 1 等待(因为孩子 2 和孩子 3 仍然有一个打开的写描述符)并且孩子 2 等待(因为孩子 3 仍然有一个打开的写描述符);只有孩子 3 睡觉并退出。这会导致其文件描述符关闭,从而唤醒子 2。然后子 2 休眠并退出,关闭其文件描述符,最终唤醒子 1。


    如果你想避免这种行为,你必须关闭每个孩子中打开的文件描述符:

        else {
            for (int j = 0; j < i; j++) {
                close(childs[j]);
            }
            close(p[1]);
            printf("child %d start\n", i + 1);
    

    【讨论】:

      【解决方案2】:

      管道的写入端被子级继承。 由于 filedescriptor 是 ref-counted,只有当所有对它的引用都关闭时,写端才被认为是关闭的。

      以下是您的代码,经过轻微重构,并添加了修复:

      #include <stdlib.h>
      #include <stdio.h>
      #include <unistd.h>
      #include <sys/types.h>
      #include <sys/wait.h>
      
      int main(int argc, char **argv) {
      
          int children_w[3];
      
          for (int i = 0; i < 3; ++i) {
              int p[2];
      
              if (0>pipe(p)) 
                  { perror("pipe"); exit(1); }
      
              pid_t pid;
              if(0> (pid= fork()))
                  { perror("fork"); exit(1); }
      
              if(pid==0) {
                  /* Fix -- close the leaked write ends  */
                  int j;
                  for(j=0; j<i; j++)
                      close(children_w[j]);
                  /* end fix*/ 
                  close(p[1]);
                  printf("child %d start\n", i + 1);
      
                  char buf[10];
                  buf[0] = 0;
                  int r;
                  if ((r = read(p[0], buf, 9)) == -1) { perror("read");/*...*/ }
      
                  printf("child %d read %s (%d), finish\n", i + 1, buf, r);
      
                  sleep(2);
                  exit(0);
              }
              children_w[i] = p[1];
              close(p[0]);
          }
      
          for (int i = 0; i < 3; ++i) {
          //        if (argc > 1) {
          //            write(childs[i], "42", 2);
          //        }
          // ============== HERE >>>
              close(children_w[i]);
          }
      
          pid_t pid;
          while ((pid = waitpid(-1, NULL, 0)) > 0) {
               printf("child %d exited\n", pid);
          }
      
          return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-12-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-30
        相关资源
        最近更新 更多