簡體   English   中英

為什么 IPC::Open3 會陷入僵局?

[英]Why does IPC::Open3 get deadlocked?

我瀏覽了open3的文檔,這是我無法理解的部分:

如果您嘗試從孩子的 stdout 編寫器和他們的 stderr 編寫器讀取,您將遇到阻塞問題,這意味着您將要使用 select() 或 IO::Select,這意味着您最好使用 sysread( ) 而不是普通內容的 readline() 。

這是非常危險的,因為您可能會永遠阻塞。 它假設它要與 bc 之類的東西對話,既寫入又讀取。 這大概是安全的,因為您“知道”像 bc 這樣的命令將一次讀取一行,而 output 一次讀取一行。 然而,像 sort 這樣的程序首先讀取其整個輸入 stream 很容易導致死鎖。

所以我嘗試了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 個字符的文本文件(我的機器的限制)。

然后我查看了這個FAQ,下面是我的新版本的 execute:

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. 此外,當我將詞法變量( my $out而不是OUT )用於文件句柄時, good_execute不起作用。 它給出了這個錯誤: open3: open(GLOB(0x610920), >&main::OUT) failed: Invalid argument 為什么這樣?
  4. 似乎在給定時間只有一個文件句柄可以寫入,如果我丟棄持有資源的句柄,其他句柄將繼續等待。 我以前一直認為STDERR和STDOUT是獨立的流,不共享任何資源。 我想我的理解在這里有點缺陷。 請給我一些關於這方面的指示。

您已經遇到了我在文檔中提到的問題,而且還遇到了一些。 你陷入僵局是因為你在等待孩子退出,然后再閱讀它。 如果它有超過 output 的 pipe 緩沖區,它將阻塞並下一次退出。 另外,您還沒有關閉手柄的末端。

你還有其他錯誤。 您不能以這種方式在句柄上測試 output,因為您只是執行了一個阻塞的 readline 並丟棄了它的結果。 此外,如果您嘗試在 stdout 之前讀取所有 stderr,並且如果 stdout 上有超過 pipe 緩沖區 output,那么您的孩子將阻止寫入 stdout 而您阻止從他的 stderr 讀取。

您確實必須使用selectIO::Select才能正確執行此操作。 您只能在該句柄上有 output 可用時從該句柄讀取,並且您也不能將緩沖調用與select混合使用,除非您非常幸運。

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

pipe 可能會變滿,阻塞孩子; 但是父母很快就會過來清空它,解除孩子的阻塞。 沒有僵局。


">&OUT"的計算結果為>&OUT (沒有要插入的變量)

">&$OUT"的計算結果為>&GLOB(0x########) (你插入$OUT 。)

有一種傳遞詞法文件句柄(或者更確切地說是它的描述符)的方法,但是有一個關於它們的錯誤,所以我總是將 package 變量與open3一起使用。


STDOUT 和 STDERR 是獨立的(除非您執行類似2>&1的操作,即使那樣,它們也會有單獨的標志和緩沖區)。 如果您發現它們不是,那么您就得出了錯誤的結論。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM