为什么IPC :: Open3会死锁?

问题描述:

我通过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是独立的流,并没有共享任何资源。我想我的理解在这里有点有缺陷。请给我一些指示。

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

您还有其他错误。你不能以这种方式测试句柄上的输出,因为你只是做了一个阻塞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

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