1

私は2つの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によってキックオフされる可能性があるため、別のカスタマイズされた信号を使用しました。そして今はうまく機能しています。opensystemUSR1

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

上記の変更にはまだ問題があります。分岐したサブプロセスは、終了する前に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 |"別の子プロセスを開始します。結局、メインプロセスには孫と曽孫の2人の子供がいます。

残念ながら、使用を開始したopen、またはsystem暗黙的waitにアタッチされた子はCHLD、完了するとシグナルを開始しますが、ハンドラーが実行されると、待機するものがないため、ご覧のようにハングします。

perldoc perlipcこれは言う

注意:qx()、system()、および外部コマンドを呼び出すための一部のモジュールは、fork()を実行してから、結果を待ちます()。したがって、シグナルハンドラが呼び出されます。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);
  }
}
于 2013-01-21T08:24:48.950 に答える
1

オプション1

pipe必要なことを行う1つの方法は、とで作成された1対の半二重パイプと同期することです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
 子:月1月21日18:10:42 2013
親:2013年1月21日月曜日18:10:43
 子:月1月21日18:10:44 2013
親: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 に答える