从子进程到控制台的fork()和STDOUT/STDERR

9
我正在编写一个程序,它会fork多个子进程,并希望所有这些子进程都能够在不混乱输出的情况下写入STDERRSTDOUT。我没有做任何花哨的事情,只是发出以换行符结尾的行(在我的理解中,这应该是Linux上的原子操作)。从perlfaq中可以看到:

主进程和后台进程(“子”进程)共享相同的STDIN、STDOUT和STDERR文件句柄。如果两者同时尝试访问它们,可能会发生奇怪的事情。您可能需要关闭或重新打开这些文件句柄以供子进程使用。您可以通过打开管道(参见open)来解决这个问题,但在某些系统上,这意味着子进程无法生存于父进程之外。

它说我应该为子进程“关闭或重新打开”这些文件句柄。关闭很简单,但是“重新打开”是什么意思?我尝试了类似于以下代码,但它并不起作用(输出仍然混乱):

open(SAVED_STDERR, '>&', \*STDERR) or die "Could not create copy of STDERR: $!";
close(STDERR);

# re-open STDERR
open(STDERR, '>&SAVED_STDERR') or die "Could not re-open STDERR: $!";

那么,我在这方面做错了什么?它所提到的管道示例是什么样子的?有没有更好的方法来协调多个进程的输出到控制台?

2个回答

12

对于标准输出和标准输入,文件句柄的写入不是原子性的。虽然有一些特殊情况(例如FIFO),但这并不是你当前所遇到的问题。

当它说重新打开标准输出时,意思是“创建一个新的标准输出实例”。 这个新实例与父代的不同。这就是您可以在系统上打开多个终端而不将所有标准输出发送到同一位置的方式。

使用管道方法可以通过管道(类似于shell中的 |)连接子进程和父进程,并且您需要让父进程从管道读取数据并自己复用输出。 父进程负责从管道中读取数据,并确保不会同时交错来自管道和输出到父进程的标准输出的内容。 在此处有关于管道的示例和说明。

示例代码:

use IO::Handle;

pipe(PARENTREAD, PARENTWRITE);
pipe(CHILDREAD, CHILDWRITE);

PARENTWRITE->autoflush(1);
CHILDWRITE->autoflush(1);

if ($child = fork) { # Parent code
   chomp($result = <PARENTREAD>);
   print "Got a value of $result from child\n";
   waitpid($child,0);
} else {
   print PARENTWRITE "FROM CHILD\n";
   exit;
}

看看子进程如何不直接向标准输出写入信息,而是通过管道发送消息给父进程,由父进程使用自己的标准输出进行写入。请注意,我省略了关闭不必要的文件句柄等细节。


我最终选择了IO::Pipe和AnyEvent来处理管道和IO选择,但似乎很有效。谢谢。 - mpeters

2
虽然这并不能帮助你解决混乱的问题,但我花了很长时间才找到一种启动子进程的方法,可以由父进程进行写入,并且子进程的stderr和stdout会直接发送到屏幕上(这解决了在不使用像select这样的高级技巧时读取两个不同FD的阻塞问题)。
一旦我弄清楚了,问题就变得微不足道了。
my $pid = open3(*CHLD_IN, ">&STDERR", ">&STDOUT", 'some child program');
# write to child
print CHLD_IN "some message";
close(CHLD_IN);
waitpid($pid, 0);

从“某个子程序”发出的所有内容都将被发送到stdout/stderr,您只需通过写入CHLD_IN来轻松地传输数据,并相信如果子进程的缓冲区填满,它会阻塞。对于父进程的调用者,所有这些都只是看起来像stderr/stdout。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接