分支,是的!第二部分

在这篇文章的第一部分中,我描述了如何使用Perl的fork函数编写并发程序。这里还有其他几种方法。

WNOHANG

通常,waitpid是一个阻塞调用,当子进程退出时返回

#!/usr/bin/perl

my $pid = fork;

if ($pid == 0) {
  sleep 1;
  exit;
}

waitpid $pid, 0;

在这个例子中,waitpid的第二个参数是0,这是参数的标志。但如果我们想在父进程中执行额外的处理,同时偶尔检查是否回收了子进程怎么办?

POSIX模块包含了POSIX模块中的WNOHANG常量,这使得waitpid调用非阻塞。相反,它会立即返回一个整数

  • -1表示没有子进程存在于该进程ID中,或者如果没有提供进程ID为-1,则没有子进程
  • 0表示存在子进程,但它尚未改变状态
  • 2-32768是已退出的子进程的进程ID(它永远不会是1 - 那是init

    #!/usr/bin/perl
    use POSIX 'WNOHANG';
    
    my $pid = fork;
    
    if ($pid == 0) {
    sleep 1;
    exit;
    }
    
    my $kid;
    do {
    # do additional processing
    sleep 1;
    
    $kid = waitpid -1, WNOHANG;
    } while ($kid == 0);

我已经修改了代码,在do while循环中等待子进程退出,每次迭代都调用带有WNOHANGwaitpid,这样我就可以在do块的主体中执行任何额外的处理。没有WNOHANG,这个循环会为每个被回收的子进程迭代一次;有了它,我仍然可以收集退出的子进程,但循环可能在同时迭代成千上万次。

WUNTRACED

POSIX模块提供了waitpid常量和宏。另一个常量是WUNTRACED,它会导致waitpid在子进程停止(但未退出)时返回。

然后父进程可以采取适当的行动:它可能将停止的进程记录在某个地方,或者选择通过发送继续信号(SIGCONT)来恢复子进程。

#!/usr/bin/perl
use POSIX ':sys_wait_h';
$SIG{INT} = sub { exit };

my $pid = fork;

if ($pid == 0) {
  while (1) {
    print "child going to sleep\n";
    sleep 10;
  }
}

print "child PID: $pid\n";
while (1) {
  my $kid = waitpid $pid, WUNTRACED;

  if (WIFSTOPPED(${^CHILD_ERROR_NATIVE})) {
    print "sending SIGCONT to child\n";
    kill 'CONT', $kid;
  }
  else {
    exit;
  }
}

我使用了组sys_wait_h从POSIX模块导入多个符号。这一次,父亲和子进程都在无限while循环中。如果我通过发送SIGSTOP来暂停子进程,waitpid将返回。父进程通过宏WIFSTOPPED检查子进程是否已停止,如果是,则通过kill向子进程发送SIGCONT,恢复它。

wuntraced.pl运行脚本

$ ./wuntraced.pl
child PID: 15013
child going to sleep

在另一个终端中,我向子进程发送SIGSTOP

kill -s STOP 15013

然后父进程恢复子进程

sending SIGCONT to child
child going to sleep

这两个进程将一直运行,直到我向子进程发送SIGINT

$ kill -s INT 15013

组合常量

WNOHANG和WUNTRACED不是互斥的:我可以通过将这两个常量组合成一个单一的标志值来改变waitpid的行为,使用二进制或(|)。

#!/usr/bin/perl
use Data::Dumper 'Dumper';
use POSIX ':sys_wait_h';

$SIG{INT} = sub { exit };
my %pids;

for (1..3) {
  my $pid = fork;
  if ($pid == 0) {
    sleep 1 while 1;
  }
  else {
    $pids{$pid} = {
      duration => undef,
      started  => time,
      stops    => 0,
    };
  }
}

while (1) {
  my $kid = waitpid -1, WNOHANG | WUNTRACED;

  # do additional processing
  print Dumper(\%pids);
  sleep 3;

  if (WIFSTOPPED(${^CHILD_ERROR_NATIVE})) {
    $pids{$kid}->{stops}++;
    kill 'CONT', $kid;
  }
  elsif ($kid > 0) {
    my $exit_time = time;
    $pids{$kid}->{duration} = $exit_time - $pids{$kid}->{started};
  }
  elsif ($kid == -1) {
    exit;
  }
}

此代码创建了3个子进程,它们将永远运行,父进程跟踪每个子进程的统计数据:开始时间、持续时间以及它收到SIGSTOP的次数。父进程将使用SIGCONT恢复任何停止的子进程。父进程每3秒打印一次统计数据,当所有子进程都退出时退出。

运行此代码,我可以通过向不同的子进程发送SIGSTOP和SIGINT来玩弄,并观察统计数据更新。尽管这是一个简单的示例,但通过使用WNOHANGWUNTRACED,你可以看到它们如何将父进程的角色从被动的观察者转变为可以主动管理其子进程的管理员。

标签

David Farrell

David是一位专业程序员,他经常推文博客关于代码和编程的艺术。

浏览他们的文章

反馈

这篇文章有什么问题吗?请帮助我们通过在GitHub上打开问题或拉取请求来解决问题。