2

所以我有一个程序,我可以让一些孩子做一些有用的任务。然后,我生成了另一个需要等待第一个孩子停下来才能完成工作的孩子。然后父程序继续运行,最后等待最后一个分叉的子程序停止。

我遇到了一个需要等待其他人的孩子没有的问题。

use strict;
use warnings;
use diagnostics;

my $pid1;
my $child1 = fork();
if ($child1) {
    # parent
    #print "pid is $pid, parent $$\n";
    $pid1 = $child1;
} elsif ($child1 == 0) {
        # child1
        # do something
        sleep 20;
        print "Child1\n";

        exit 0;
} else {
        die "couldnt fork: $!\n";
}

my $pid2;
my $child2 = fork();
if ($child2) {
    # parent
    #print "pid is $pid, parent $$\n";
    $pid2 = $child2;
} elsif ($child2 == 0) {
        # child2
        # wait for child1 to finish
        my $tmp = waitpid($pid1, 0);

        # do something else
        print "Child2\n";

        exit 0;
} else {
        die "couldnt fork: $!\n";
}

# do more stuff

# wait for child2 to finish
my $tmp = waitpid($pid2, 0);

是否有捷径可寻?可能不必将第一个孩子包裹在第二个孩子中?

4

4 回答 4

2

在类 Unix 系统中,给定进程只能等待其自己的子进程死亡。它不能等待兄弟姐妹、祖先或孙子死去。

于 2013-01-18T22:38:35.180 回答
2

生成一个子进程,让该进程生成子子进程,然后等待它们完成,然后再继续第一个子进程。

然后让父进程完成它的工作,并在子进程准备好等待时等待它。

于 2013-01-18T23:47:03.377 回答
1

假设您perlsemop朋友,您可以使用 System V 信号量在孩子之间进行同步。请参阅下面的工作示例程序。

我们从通常的正面问题开始。代码使用内置的IPC::SysVIPC::Semaphore模块,而不是直接调用低级信号量操作 。

#! /usr/bin/env perl

use strict;
use warnings;

use IPC::Semaphore;
use IPC::SysV qw/ IPC_PRIVATE S_IRUSR S_IWUSR IPC_CREAT /;

该程序将子进程分为两个阶段。第一阶段的子进程运行到完成,在没有同步问题的情况下执行他们的处理。我们可以拥有任意数量的这些。

我们有一个第二阶段进程,但它在所有 第一阶段子进程完成后执行。

下面是简单的占位符实现。

# how many other children the last child must wait for
my $FIRST_STAGE_CHILDREN = 2;

sub first_stage {
  my($id) = @_;

  print "[$$] hello from child $id\n";
  sleep rand 10;
  print "[$$] child $id done\n";
}

sub second_stage {
  print "[$$] hello from second-stage child!\n";
}

为了实现第一阶段和第二阶段之间的同步,程序创建了一组信号量,其大小等于第一阶段子节点的数量。当第一阶段的孩子完成时,程序会释放对应于该孩子的特定信号量。

my $sem = IPC::Semaphore->new(
  IPC_PRIVATE, $FIRST_STAGE_CHILDREN,
  S_IRUSR | S_IWUSR | IPC_CREAT)
    or die "$0: failed to create semaphore: $!";

正如我们稍后将看到的,第二阶段的孩子试图减少他们的信号量来等待他的兄弟。通过将值从零开始,当第二阶段的孩子尝试这些减量时,操作系统将使孩子进入睡眠状态,因为。只有在所有第一阶段孩子都退出并释放信号量后,系统才会解锁第二阶段孩子。

# start in blocked state
$sem->setall((0) x $FIRST_STAGE_CHILDREN);

首先我们fork是第一阶段的孩子。在这种设计中,父进程会尽可能多地进行簿记。这保持了first_stagesecond_stage简单的定义。此外,如果第一阶段的孩子在没有释放信号量的情况下以某种方式退出,那么第二阶段将没有运行的希望。

my %kids;
foreach my $id (0 .. $FIRST_STAGE_CHILDREN - 1) {
  my $pid = fork;
  die "$0: fork: $!" unless defined $pid;

  if ($pid) {
    ++$kids{$pid};
  }
  else {
    first_stage $id;
    $sem->op($id, 1, 0);  # release
    exit 0;
  }
}

现在我们分叉第二阶段的孩子。重要提示:虽然代码对多个信号量执行操作,但这会原子地发生,也就是说,它要么对所有信号量起作用,要么对它们都不起作用。在任何可观察的状态下,似乎第二阶段能够抓取的信号量都少于第一阶段的所有信号量。这是一个重要的属性。在更复杂的系统中,随意的 onesie-twosie 获取和释放将导致死锁。

my $pid = fork;
die "$0: fork: $!" unless defined $pid;

if ($pid) {
  ++$kids{$pid};
}
else {
  # block waiting on all first-stage children
  my @op = map +($_, -1, 0), 0 .. $FIRST_STAGE_CHILDREN - 1;
  $sem->op(@op);

  second_stage;
  exit 0;
}

最后,父进程等待所有子进程完成。

do {
  $pid = waitpid -1, 0;
  print "[$$] reaped $pid\n";
  warn "$0: unknown child $pid" unless delete $kids{$pid};
} while $pid > 0 && keys %kids;

示例输出如下。在可以看到停顿的地方观看直播会更有趣。

[18389] 来自孩子的你好 0
[18390] 孩子 1 你好
[18390] 孩子 1 完成
[18388] 收获 18390
[18389] 孩子 0 完成
【18391】二期孩子你好!
[18388] 收获 18389
[18388] 收获 18391
于 2013-01-19T15:30:31.513 回答
1

最简单的方法是使用Forks::Super.

use Forks::Super;
my $child1 = fork();
if ($child1 != 0) {
    # ... parent code ...
} else {
    # ... child code ...
    exit;
}

my $child2 = fork { 
    depend_on => $child1, 
    on_busy => 'queue',
    sub => sub {
       # ... code to execute in 2nd child ...
    }
};
# ... more code to run in the parent ...
# ... and at the end of the program:
waitall;

Forks::Super,waitpid仍然在父级中调用(在幕后)。但是当第一个子进程完成后,Forks::Super就会知道是时候在后台启动第二个子进程了。

于 2013-01-20T01:37:53.340 回答