2012-04-05 77 views
9

我通過open3文檔去了,這裏要說的是我無法理解的部分:爲什麼IPC :: Open3會死鎖?

If you try to read from the child's stdout writer and their stderr writer, you'll have problems with blocking, which means you'll want to use select() or the IO::Select, which means you'd best use sysread() instead of readline() for normal stuff.

This is very dangerous, as you may block forever. It assumes it's going to talk to something like bc, both writing to it and reading from it. This is presumably safe because you "know" that commands like bc will read a line at a time and output a line at a time. Programs like sort that read their entire input stream first, however, are quite apt to cause deadlock.

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

然後我看着this常見問題,以下是我執行的新版本:

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,經過一些google搜索,想出了這個版本的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是獨立的流,並沒有共享任何資源。我想我的理解在這裏有點有缺陷。請給我一些指示。

回答

13

您遇到了我在文檔中寫到的問題,然後是一些問題。由於您在閱讀之前正在等待孩子退出,所以您會陷入僵局。如果它有多個輸出管道緩衝區,它將阻塞並且下一個退出。另外你還沒有關閉手柄的末端。

您還有其他錯誤。你不能以這種方式測試句柄上的輸出,因爲你只是做了一個阻塞readline並放棄了它的結果。此外,如果你試圖在stdout之前讀取所有的stderr,並且如果stdout上有多個輸出管道緩衝區,那麼你的孩子將阻止寫入標準輸出,同時阻止從他的stderr讀取數據。

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

+0

我讀了'IO :: Select'模塊,並更新了我的問題... – Unos 2012-04-05 15:12:31

+0

@Unos你有很多問題。你應該只問一個問題。我確實已經回答了最初的問題,但你又問了同樣的問題,就好像你沒有注意到的一樣。我想,回答所有新問題需要在每個程序中爲您的代碼的每一行添加一段或三段。要求某人的工作量很大,當然要超過一小時,而且最有可能要花費三個小時的免費工作。我今天沒有那個時間。請研究我已經說過的話,因爲我沒有看到它被沉沒。 – tchrist 2012-04-05 17:02:05

+0

嗨@tchrist,我幾乎不想激怒你。在更新後我沒有刪除我之前的問題,因爲我認爲如果我這樣做,答案可能會丟失。我一定會更詳細地研究這一點。 – Unos 2012-04-06 04:05:03

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. 

僵局!


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,即使這樣,他們將有獨立的標誌和緩衝器)。如果你發現它們不是,你會得出錯誤的結論。

+0

感謝您的照片,@ikegami。我在一個完全不同的方向思考。但現在它是有道理的。 – Unos 2012-04-06 03:59:29