我编写了两个perl脚本(parent.pl和child.pl),其源代码如下:
parent.pl:
# file parent.pl
$SIG{CHLD} = sub {
while(waitpid(-1, WNOHANG) > 0) {
print "child process exit\n";
}
};
my $pid = fork();
if($pid == 0) {
system("perl child.pl");
exit;
}
while(1) {
open my $fh, "date |";
while(<$fh>) {
print "parent: ".$_;
}
close $fh;
sleep(2);
}
child.pl
#file child.pl
while(1) {
open my $fh, "date |";
while(<$fh>) {
print " child: ".$_;
}
close $fh;
sleep(2);
}
我想要的是父进程和分叉子进程交替输出当前日期。但是当我运行perl parent.pl
时,输出是这样的:
$ perl parent.pl
parent: Mon Jan 21 14:53:36 CST 2013
child: Mon Jan 21 14:53:36 CST 2013
child: Mon Jan 21 14:53:38 CST 2013
child: Mon Jan 21 14:53:40 CST 2013
child: Mon Jan 21 14:53:42 CST 2013
child: Mon Jan 21 14:53:44 CST 2013
打开管道似乎阻止了父进程。
但如果我删除信号CHLD的以下操作。
$SIG{CHLD} = sub {
while(waitpid(-1, WNOHANG) > 0) {
print "child process exit\n";
}
};
再次运行它。看来没问题。
$ perl parent.pl
parent: Mon Jan 21 14:57:57 CST 2013
child: Mon Jan 21 14:57:57 CST 2013
parent: Mon Jan 21 14:57:59 CST 2013
child: Mon Jan 21 14:57:59 CST 2013
parent: Mon Jan 21 14:58:01 CST 2013
child: Mon Jan 21 14:58:01 CST 2013
但我仍感到困惑。当我尝试打开管道时,为什么父进程被阻止了?
我认为删除SIG {CHLD}功能不是一个好主意,因为应该检索僵尸进程。
任何人都可以帮助我吗?非常感谢你!
=============================================== ===================
感谢@Borodin帮助我解决我的难题。我试图像这样修改parent.pl
:
my $main_pid = $$;
$SIG{USR1} = sub {
#sleep(1);
while(waitpid(-1, WNOHANG) > 0) {
print "child process exit\n";
}
};
my $pid = fork();
if($pid == 0) {
$SIG{USR1} = 'IGNORE';
system("perl child.pl");
kill USR1, $main_pid;
exit;
}
while(1) {
open my $fh, "date |";
while(<$fh>) {
print "parent: ".$_;
}
close $fh;
sleep(2);
}
由于CHLD
或open
可能会启动system
信号,因此我使用了另一个自定义信号USR1
。它现在运作良好。
=============================================== =========================
上述修改仍存在问题。分叉子进程在退出之前发送USR1信号。可能是父进程应该在waitpid
之前休眠一段时间,因为子进程还没有退出。
我现在不手动检索子流程,并设置$SIG{$CHLD} = 'IGNORE'
。希望操作系统退出时可以检索子流程。
答案 0 :(得分:2)
由于open my $fh, "date |"
和system("perl child.pl")
正在启动子进程以及显式fork
,因此要复杂得多。
所以fork
开始一个子进程,它system("perl child.pl")
启动它自己的子进程,而进程又进行open my $fh, "date |"
,这会打开另一个子进程,现在这个进程很大 - 主要父母过程的孙子。
同时主进程自己执行open my $fh, "date |"
,启动另一个子进程。最后,主要过程有两个孩子,一个孙子和一个曾孙。
不幸的是,使用open
或system
开始使用的孩子会附加一个implcit wait
,因此当他们完成时会发出CHLD
信号但是执行处理程序没有什么可以等待的,所以它会像你看到的那样挂起。
perldoc perlipc
有这个说法
注意:qx(),system()和一些用于调用外部命令的模块执行fork(),然后执行wait()以获得结果。因此,将调用您的信号处理程序。因为wait()已经被system()或qx()调用,所以信号处理程序中的wait()将不会再看到僵尸,因此会阻塞。
你可以通过只保留一个父母和一个子进程来实现目标。
use strict;
use warnings;
use POSIX ':sys_wait_h';
STDOUT->autoflush;
$SIG{CHLD} = sub {
while(waitpid(-1, WNOHANG) > 0) {
print "child process exit\n";
}
};
my $pid = fork();
if ($pid == 0) {
while(1) {
printf " child: %s\n", scalar localtime;
sleep(2);
}
}
else {
while(1) {
printf "parent: %s\n", scalar localtime;
sleep(2);
}
}
答案 1 :(得分:1)
执行所需操作的一种方法是与使用pipe
和open
创建的一对半双工管道同步。使用全双工socketpair
可以简化簿记。
在"|-"
上隐式fork
打开句柄,其子标签输入是管道的读取端,写入结束是返回父节点的文件句柄。父级使用此隐式管道释放子级,并使用显式创建的管道作为反向通道。
#! /usr/bin/env perl
use strict;
use warnings;
use Fcntl qw/ F_GETFD F_SETFD FD_CLOEXEC /;
use IO::Handle;
pipe my $fromchild, my $toparent or die "$0: pipe: $!";
$_->autoflush(1) for $toparent, $fromchild;
my $flags = fcntl $toparent, F_GETFD, 0 or die "$0: fcntl: $!";
fcntl $toparent, F_SETFD, $flags & ~FD_CLOEXEC or die "$0: fcntl: $!";
my $pid = open my $tochild, "|-";
$tochild->autoflush(1);
die "$0: fork: $!" unless defined $pid;
if ($pid != 0) {
while (1) {
print "parent: ", scalar localtime, "\n";
sleep 1;
print $tochild "over\n";
chomp($_ = <$fromchild>);
exit 0 if $_ eq "over and out";
}
}
else {
exec "child.pl", fileno $toparent
or die "$0: exec: $!";
}
child.pl
中的代码如下。请注意,父级传递文件描述符,孩子必须dup
与另一方向的父级进行通信。
#! /usr/bin/env perl
use strict;
use warnings;
use IO::Handle;
my($fd) = @ARGV or die "Usage: $0 to-parent-fd\n";
open my $toparent, ">&=", $fd or die "$0: dup: $!";
$toparent->autoflush(1);
my $rounds = 5;
for (1 .. $rounds) {
my $over = <STDIN>;
print " child: ", scalar localtime, "\n";
sleep 1;
print $toparent ($_ < $rounds ? "over\n" : "over and out\n");
}
exit 0;
在演唱会上,他们看起来像
parent: Mon Jan 21 18:10:39 2013 child: Mon Jan 21 18:10:40 2013 parent: Mon Jan 21 18:10:41 2013 child: Mon Jan 21 18:10:42 2013 parent: Mon Jan 21 18:10:43 2013 child: Mon Jan 21 18:10:44 2013 parent: Mon Jan 21 18:10:45 2013 child: Mon Jan 21 18:10:46 2013 parent: Mon Jan 21 18:10:47 2013 child: Mon Jan 21 18:10:48 2013
稍微更奇特的安排是让子过程安排在一个环或循环中相互轮流。在父进程和子进程之间来回只是一个长度为2的循环。
#! /usr/bin/env perl
use strict;
use warnings;
use IPC::SysV qw/ IPC_CREAT IPC_PRIVATE S_IRUSR S_IWUSR /;
use IPC::Semaphore;
my $WORKERS = 3;
给定的工作者从集合中获取自己的信号量,但在完成后释放 next 工作者。
sub take {
my($id,$sem) = @_;
$sem->op($id, -1, 0) or die "$0: semop: $!";
}
sub release {
my($id,$sem) = @_;
my $next = ($id + 1) % $WORKERS;
$sem->op($next, 1, 0) or die "$0: semop: $!";
}
sub worker {
my($id,$sem) = @_;
for (1 .. 3) {
take $id, $sem;
print "[worker $id]: ", scalar localtime, "\n";
sleep 1;
release $id, $sem;
}
}
创建信号量集并让第一个准备好运行。
my $sem = IPC::Semaphore->new(
IPC_PRIVATE,
$WORKERS,
IPC_CREAT | S_IRUSR | S_IWUSR)
or die "$0: semget: $!";
$sem->setall((0) x $WORKERS);
$sem->setval(0, 1); # unblock first only
现在我们已准备好fork
子进程并让它们执行。
foreach my $id (0 .. $WORKERS - 1) {
my $pid = fork;
die "$0: fork: $!" unless defined $pid;
if ($pid == 0) {
worker $id, $sem;
exit 0;
}
}
# wait on all workers to finish
my $pid;
do {
$pid = waitpid -1, 0;
} while $pid > 0;
示例输出:
[worker 0]: Mon Jan 21 18:13:27 2013 [worker 1]: Mon Jan 21 18:13:28 2013 [worker 2]: Mon Jan 21 18:13:29 2013 [worker 0]: Mon Jan 21 18:13:30 2013 [worker 1]: Mon Jan 21 18:13:31 2013 [worker 2]: Mon Jan 21 18:13:32 2013 [worker 0]: Mon Jan 21 18:13:33 2013 [worker 1]: Mon Jan 21 18:13:34 2013 [worker 2]: Mon Jan 21 18:13:35 2013