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

Uno*_*nos 9 perl ipc

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

如果你试图从孩子的stdout编写器和他们的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>);
}
Run Code Online (Sandbox Code Playgroud)

值得注意的是,我必须$err在这里进行初始化.

无论如何,当我execute("sort $some_file");给出$some_file的文本文件包含超过4096个字符(我的机器的限制)时,这就会挂起.

然后我查看了这个 FAQ,下面是我的新版执行:

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>);
}
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

这工作正常,现在有些事情变得更加清晰了.但整体情况仍然有点朦胧.

所以我的问题是:

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

tch*_*ist 13

你遇到过我在文档中写的问题,然后是一些问题.你正在陷入困境,因为你在等待孩子读书之前就等了.如果它有超过输出的管道缓冲区,它将阻塞并下一次退出.另外,你没有关闭手柄的末端.

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

你真的必须使用select,或者IO::Select,正确地做到这一点.只有在该句柄上有可用输出时,您才能从句柄中读取,并且select除非您非常幸运,否则您不得将缓冲的呼叫与其混合.


ike*_*ami 7

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.
Run Code Online (Sandbox Code Playgroud)

僵局!


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
Run Code Online (Sandbox Code Playgroud)

管道可能会变满,阻塞孩子; 但父母很快就会把它清空,让孩子畅通无阻.没有死锁.


">&OUT"评估为>&OUT.(没有要插入的变量)

">&$OUT"评估为>&GLOB(0x########).(你插入$OUT.)

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


STDOUT和STDERR是独立的(除非你做类似的事情2>&1,即使这样,它们也会有单独的标志和缓冲区).如果你发现他们不是,那你得出了错误的结论.