Perl 为什么IPC::Open3会陷入僵局?

Perl 为什么IPC::Open3会陷入僵局?,perl,ipc,Perl,Ipc,我查阅了的文档,以下是我无法理解的部分: 如果你试着从孩子的标准写手和他们的标准读物上读 writer,您将遇到阻塞问题,这意味着您需要 使用select()或IO::select,这意味着您最好使用sysread() 而不是用于普通内容的readline() 这是非常危险的,因为你可能会永远封锁。它假定它是 去和bc这样的人交谈,既写又读 从它。这大概是安全的,因为您“知道”命令 bc一次读取一行,一次输出一行。程序 然而,像首先读取整个输入流的排序一样 很容易造成僵局 所以我尝试了open3

我查阅了的文档,以下是我无法理解的部分:

如果你试着从孩子的标准写手和他们的标准读物上读 writer,您将遇到阻塞问题,这意味着您需要 使用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>);
}
sort
命令现在执行得很好,但我不知道为什么

[Update]在阅读了@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);
}
 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.
 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
sub-good\u执行{
我的($cmd)=@;
打印“[命令]:$cmd\n”;
my$pid=open3(my$in,my$out,my$err=gensym(),$cmd);
打印“[PID]:$PID\n”;
my$sel=新IO::Select;
$sel->add($out,$err);
而(my@fhs=$sel->can_read){
每个我的$fh(@fhs){
我的$line=;
除非(定义为$line){
$sel->remove($fh);
下一个
}
如果($fh==$out){
打印“[输出]:$line”;
}elsif($fh==$err){
打印“[错误]:$line”;
}否则{
死“[错误]:这永远不会执行!”;
}
}
}
waitpid($pid,0);
}
这很好,现在有几件事情变得更清楚了。但总体情况仍有点模糊

因此,我的问题是:

  • 挂起执行有什么问题
  • 我想
    good\u execute
    之所以有效,是因为open3调用中的
    &
    。但是为什么以及如何
  • 另外,当我对文件句柄使用词法变量(
    my$out
    而不是
    out
    )时,
    good\u execute
    不起作用。它给出了以下错误:
    open3:open(GLOB(0x610920),>&main::OUT)失败:参数无效
    。为什么会这样
  • 在给定的时间内,似乎只有一个文件句柄可以写入,如果我放弃保留资源的句柄,其他句柄将继续等待。我曾经认为STDERR和STDOUT是独立的流,不共享任何资源。我想我的理解有点错误。在这方面也请给我一些建议

  • 您遇到了我在文档中提到的问题,然后是一些问题。您处于死锁状态,因为您正在等待孩子退出,然后再阅读。如果它有一个以上的输出管道缓冲区,它将阻塞和下一个出口。此外,你还没有关闭你的两端的处理

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


    您必须使用
    select
    ,或
    IO::select
    ,才能正确执行此操作。只有当句柄上有可用的输出时,才能从该句柄读取数据,并且也不能将缓冲调用与
    select
    混合使用,除非您非常幸运。

    hung\u 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);
    }
    
     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.
    
     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
    
    死锁


    执行良好

    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);
    }
    
     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.
    
     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
    
    管道可能会满,堵塞孩子;但家长很快就会过来清空它,解除孩子的阻碍。没有僵局


    “>&OUT”
    的计算结果为
    &OUT
    。(无需插入变量)

    “>”和$OUT“
    的计算结果为
    &GLOB(0x######)
    。(您插入了
    $OUT

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



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

    @Unos你有很多问题。你应该只问一个问题。我已经回答了最初的问题,但是你又问了同样的问题,好像你没有注意到一样。我想回答你所有的新问题需要在每一个程序中几乎每行代码都有一段或三段。这是一个很大的工作要求某人,肯定超过一个小时,最有可能是三个小时的免费工作。我只是今天没有时间。请仔细研究我已经说过的话,因为我看不出它已经深入人心了。嗨@tchrist,我几乎不想激怒你。更新后,我没有删除我之前的问题,因为我认为如果删除,答案可能会失去上下文。我肯定会更详细地研究这一点。我一直在四处阅读,试图找到
    waitpid($pid,0)挂起的原因,当我读到这些词时,我给了你更高的票数你处于死锁状态,因为你正在等待孩子退出,然后再阅读。谢谢你的图片,@ikegami。我当时的想法完全不同。不过,现在这是有道理的。