管道在Perl中被子进程阻塞

时间:2013-01-21 07:02:08

标签: perl fork pipe

我编写了两个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);
}

由于CHLDopen可能会启动system信号,因此我使用了另一个自定义信号USR1。它现在运作良好。

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

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

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

2 个答案:

答案 0 :(得分:2)

由于open my $fh, "date |"system("perl child.pl")正在启动子进程以及显式fork,因此要复杂得多。

所以fork开始一个子进程,它system("perl child.pl")启动它自己的子进程,而进程又进行open my $fh, "date |",这会打开另一个子进程,现在这个进程很大 - 主要父母过程的孙子。

同时主进程自己执行open my $fh, "date |",启动另一个子进程。最后,主要过程有两个孩子,一个孙子和一个曾孙。

不幸的是,使用opensystem开始使用的孩子会附加一个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)

选项1

执行所需操作的一种方法是与使用pipeopen创建的一对半双工管道同步。使用全双工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

稍微更奇特的安排是让子过程安排在一个环或循环中相互轮流。在父进程和子进程之间来回只是一个长度为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
相关问题