【问题标题】:Why does IPC::Open3 get deadlocked?为什么 IPC::Open3 会死锁?
【发布时间】:2012-04-19 05:46:18
【问题描述】:

我浏览了open3 的文档,这是我无法理解的部分:

如果您尝试从孩子的 stdout writer 和他们的 stderr 读取 作家,你会遇到阻塞问题,这意味着你会想要 使用 select() 或 IO::Select,这意味着你最好使用 sysread() 而不是 readline() 用于普通的东西。

这是非常危险的,因为您可能会永远阻塞。它假设它是 与 bc 之类的东西交谈,既写信又读 从中。这可能是安全的,因为您“知道”命令像 bc 将一次读取一行并一次输出一行。程式 然而,就像首先读取整个输入流的排序一样, 很容易造成死锁。

所以我尝试了open3,希望能更好地了解它。这是第一次尝试:

sub hung_execute {
    my($cmd) = @_;
    print "[COMMAND]: $cmd\n";
    my $pid = open3(my $in, my $out, my $err = gensym(), $cmd);
    print "[PID]: $pid\n";
    waitpid($pid, 0);
    if(<$err>) {
        print "[ERROR] : $_" while(<$err>);
        die;
    }
    print "[OUTPUT]: $_" while (<$out>);
}

有趣的是,我必须在这里初始化$err

无论如何,这只是在我 execute("sort $some_file"); 时挂起,因为 $some_file 是一个包含超过 4096 个字符的文本文件(我的机器的限制)。

然后我查看了this 常见问题解答,下面是我的新版本执行:

sub good_execute {
    my($cmd) = @_;
    print "[COMMAND]: $cmd\n";
    my $in = gensym();
    #---------------------------------------------------
    # using $in, $out doesn't work. it expects a glob?
    local *OUT = IO::File->new_tmpfile;
    local *ERR = IO::File->new_tmpfile;
    my $pid = open3($in, ">&OUT", ">&ERR", $cmd);
    print "[PID]: $pid\n";
    waitpid($pid, 0);
    seek $_, 0, 0 for \*OUT, \*ERR;
    if(<ERR>) {
        print "[ERROR] : $_" while(<ERR>);
        die;
    }
    print "[OUTPUT]: $_" while (<OUT>);
}

sort 命令现在可以正常执行,但我不知道为什么。

[更新]阅读@tchrist的回答后,我阅读了IO::Select,又在谷歌上搜索了一下,得出了这个版本的execute

sub good_execute {
    my($cmd) = @_;
    print "[COMMAND]: $cmd\n";
    my $pid = open3(my $in, my $out, my $err = gensym(), $cmd);
    print "[PID]: $pid\n";
    my $sel = new IO::Select;
    $sel->add($out, $err);
    while(my @fhs = $sel->can_read) {
        foreach my $fh (@fhs) {
            my $line = <$fh>;
            unless(defined $line) {
                $sel->remove($fh);
                next;
            }
            if($fh == $out) {
                print "[OUTPUT]: $line";
            }elsif($fh == $err) {
                print "[ERROR] : $line";
            }else{
                die "[ERROR]: This should never execute!";
            }
        }
    }
    waitpid($pid, 0);
}

这工作正常,现在有些事情变得更清楚了。但整体画面还是有点朦胧。

所以我的问题是:

  1. hung_execute 有什么问题?
  2. 我猜good_execute 工作是因为 open3 调用中的&gt;&amp;。但是为什么以及如何?
  3. 另外,当我使用词法变量(my $out 而不是OUT)作为文件句柄时,good_execute 不起作用。它给出了这个错误:open3: open(GLOB(0x610920), &gt;&amp;main::OUT) failed: Invalid argument。为什么会这样?
  4. 似乎只有一个文件句柄可以在给定时间写入,如果我丢弃持有资源的句柄,其他句柄会继续等待。我以前认为STDERR和STDOUT是独立的流,不共享任何资源。我想我的理解在这里有点缺陷。也请给我一些指示。

【问题讨论】:

    标签: perl ipc


    【解决方案1】:

    hung_execute:

     Parent                     Child
     ------------------------   ------------------------
     Waits for child to exit
                                Writes to STDOUT
                                Writes to STDOUT
                                ...
                                Writes to STDOUT
                                Tries to write to STDOUT
                                  but the pipe is full,
                                  so it blocks until the
                                  pipe is emptied some.
    

    死锁!


    good_execute:

     Parent                     Child
     ------------------------   ------------------------
     Waits for data
                                Writes to STDOUT
     Reads the data
     Waits for data
                                Writes to STDOUT
     Reads the data
     Waits for data
     ...                        ...
                                Writes to STDOUT
     Reads the data
     Waits for data
                                Exits, closing STDOUT
     Reads EOF
     Waits for child to exit
    

    管道可能会被填满,阻塞孩子;但是父母很快就会过来清空它,从而解除对孩子的阻碍。没有死锁。


    "&gt;&amp;OUT" 的计算结果为 &gt;&amp;OUT。 (没有要插入的变量)

    "&gt;&amp;$OUT" 计算结果为 &gt;&amp;GLOB(0x########)。 (你插入了$OUT。)

    有一种方法可以传递词法文件句柄(或者更确切地说是它的描述符),但是有一个关于它们的错误,所以我总是使用带有 open3 的包变量。


    STDOUT 和 STDERR 是独立的(除非您执行 2&gt;&amp;1 之类的操作,即使那样,它们也会有单独的标志和缓冲区)。如果你发现它们不是,你就会得出错误的结论。

    【讨论】:

    • 感谢您的图片,@ikegami。我在想一个完全不同的方向。不过现在说得通了。
    【解决方案2】:

    您已经遇到了我在文档中所写的问题,然后是一些问题。您正在陷入僵局,因为您在阅读之前正在等待孩子退出。如果它有多个输出管道缓冲区,它将阻塞并下一次退出。另外,您还没有关闭把手的末端。

    您也有其他错误。您不能以这种方式测试句柄上的输出,因为您只是做了一个阻塞的 readline 并丢弃了它的结果。此外,如果您尝试在 stdout 之前读取所有 stderr,并且如果 stdout 上有多个输出管道缓冲区,那么您的孩子将阻止写入 stdout,而您阻止从他的 stderr 读取。

    您确实必须使用selectIO::Select 才能正确执行此操作。您必须仅在该句柄上有可用输出时从该句柄读取,并且您也不能将缓冲调用与select 混合,除非您非常幸运。

    【讨论】:

    • @Unos 你有很多问题。你应该只问一个问题。我确实已经回答了最初的问题,但你又问了同样的问题,就好像你没有注意到一样。我猜要回答你所有的新问题,几乎每个程序中的每一行代码都需要一到三个段落。要求某人做大量的工作,肯定超过一个小时,而且很可能要进行三个小时的免费工作。我只是今天没有那个时间。请仔细研究我已经说过的内容,因为我没有看到它被沉没了。
    • 嗨@tchrist,我不想激怒你。更新后我没有删除我之前的问题,因为我认为如果我这样做了,答案可能会失去上下文。我一定会更详细地研究这个。
    • 我一直在四处阅读,试图找到有关为什么waitpid($pid, 0) was hanging and I upvoted you when I read the words; 您陷入僵局的信息,因为您在阅读之前正在等待孩子退出。`.
    猜你喜欢
    • 1970-01-01
    • 2014-07-09
    • 2015-03-09
    • 2019-12-27
    • 2019-12-16
    • 1970-01-01
    • 1970-01-01
    • 2017-02-18
    • 1970-01-01
    相关资源
    最近更新 更多