[英]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);
}
這工作正常,現在有幾件事變得更清楚了。 但整體畫面還是有些朦朧。
所以我的問題是:
hung_execute
有什么問題?good_execute
有效是因為 open3 調用中的>&
。 但為什么以及如何?my $out
而不是OUT
)用於文件句柄時, good_execute
不起作用。 它給出了這個錯誤: open3: open(GLOB(0x610920), >&main::OUT) failed: Invalid argument
。 為什么這樣?您已經遇到了我在文檔中提到的問題,而且還遇到了一些。 你陷入僵局是因為你在等待孩子退出,然后再閱讀它。 如果它有超過 output 的 pipe 緩沖區,它將阻塞並下一次退出。 另外,您還沒有關閉手柄的末端。
你還有其他錯誤。 您不能以這種方式在句柄上測試 output,因為您只是執行了一個阻塞的 readline 並丟棄了它的結果。 此外,如果您嘗試在 stdout 之前讀取所有 stderr,並且如果 stdout 上有超過 pipe 緩沖區 output,那么您的孩子將阻止寫入 stdout 而您阻止從他的 stderr 讀取。
您確實必須使用select
或IO::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.