10

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

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

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

所以我尝试了,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 个字符(我的机器的限制)的文本文件时,它就会挂起。

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

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

2 回答 2

13

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

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

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

于 2012-04-05T13:42:19.450 回答
8

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

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


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

">&$OUT"评估为>&GLOB(0x########)。(你插值了$OUT。)

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


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

于 2012-04-05T20:04:12.777 回答