1

我写了两个perl脚本(parent.pl和child.pl),它们的源代码如下:

父.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);
}

孩子.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信号可能由openor启动system,所以我使用了另一个自定义信号USR1。现在效果很好。

==================================================== =======================

上面的修改还是有问题的。分叉的子进程在退出前发送 USR1 信号。可能是父进程之前应该休眠一段时间waitpid,因为子进程还没有退出。

我现在不手动检索子流程,并设置$SIG{$CHLD} = 'IGNORE'. 希望子进程退出时能被操作系统找回。

4

2 回答 2

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了隐含的孩子wait,所以他们会CHLD在完成时启动信号,但是当处理程序被执行时,没有什么可以等待,所以它会像你所看到的那样挂起。

perldoc perlipc有这个要说

注意:qx()、system() 和一些用于调用外部命令的模块执行 fork(),然后 wait() 获取结果。因此,您的信号处理程序将被调用。因为 system() 或 qx() 已经调用了 wait(),所以信号处理程序中的 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);
  }
}
于 2013-01-21T08:24:48.950 回答
1

选项1

做你想做的事情的一种方法是与一对半双工管道同步,用pipeand创建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;

在音乐会上,他们看起来像

家长:2013 年 1 月 21 日星期一 18:10:39
 儿童:2013 年 1 月 21 日星期一 18:10:40
家长:2013 年 1 月 21 日星期一 18:10:41
 孩子:2013 年 1 月 21 日星期一 18:10:42
家长:2013 年 1 月 21 日星期一 18:10:43
 儿童:2013 年 1 月 21 日星期一 18:10:44
家长:2013 年 1 月 21 日星期一 18:10:45
 孩子:2013 年 1 月 21 日星期一 18:10:46
家长:2013 年 1 月 21 日星期一 18:10:47
 儿童:2013 年 1 月 21 日星期一 18:10:48

选项 2

一种更奇特的安排是让子进程安排在一个环或循环中彼此轮流。在父进程和子进程之间来回切换仅仅是长度为 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;

给定的工作人员从集合中获取自己的信号量,但在完成时释放下一个工作人员。

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;

样本输出:

[工人 0]:2013 年 1 月 21 日星期一 18:13:27
[工人 1]:2013 年 1 月 21 日星期一 18:13:28
[工人 2]:2013 年 1 月 21 日星期一 18:13:29
[工人 0]:2013 年 1 月 21 日星期一 18:13:30
[工人 1]:2013 年 1 月 21 日星期一 18:13:31
[工人 2]:2013 年 1 月 21 日星期一 18:13:32
[工人 0]:2013 年 1 月 21 日星期一 18:13:33
[工人 1]:2013 年 1 月 21 日星期一 18:13:34
[工人 2]:2013 年 1 月 21 日星期一 18:13:35
于 2013-01-22T00:37:28.880 回答