読者です 読者をやめる 読者になる 読者になる

fork & Parallel::ForkManager

fork

標準関数のfork()の使い方。

use strict;

if (my $pid = fork()) {
    # parent
    print "here is parent proces.\n";
} elsif ($pid == 0) {
    # child
    sleep(3);
    print "child process ends.\n";
    exit 0;
} else {
 die "fork error: $!"
}
print "parent process ends.\n";
kotaro@script$ perl fork.pl
here is parent proces.
parent process ends.
kotaro@script$ child process ends.

Parallel::ForkManager

ただ、現在はParallel::ForkManagerを使うのが普通っぽい。
http://perldoc.jp/docs/modules/Parallel-ForkManager-0.7.5/ForkManager.pod
をほぼ丸コピでコードを書いてみた。

#!/usr/bin/perl
use strict;
use Parallel::ForkManager;

my $pm = new Parallel::ForkManager(10);
$pm->run_on_start(
    sub {   my ($pid,$ident)=@_;
            print "** $ident started, pid: $pid\n";
    }
);
$pm->run_on_finish(
    sub {
        my ($pid, $exit_code, $ident) = @_;
        print "** $ident just got out of the pool ".
                "with PID $pid and exit code: $exit_code\n";
    }
);

for (1..10) {
    my $pid;
    $pid = $pm->start && next;
    # 子プロセス
    sleep(3);
    $pm->finish;
}
print "waiting child process..\n";
$pm->wait_all_children;
print "end\n"
**  started, pid: 4105
**  started, pid: 4106
**  started, pid: 4107
**  started, pid: 4108
**  started, pid: 4109
**  started, pid: 4110
**  started, pid: 4111
**  started, pid: 4112
**  started, pid: 4113
**  started, pid: 4114
waiting child process..
**  just got out of the pool with PID 4105 and exit code: 0
**  just got out of the pool with PID 4106 and exit code: 0
**  just got out of the pool with PID 4107 and exit code: 0
**  just got out of the pool with PID 4108 and exit code: 0
**  just got out of the pool with PID 4109 and exit code: 0
**  just got out of the pool with PID 4110 and exit code: 0
**  just got out of the pool with PID 4111 and exit code: 0
**  just got out of the pool with PID 4112 and exit code: 0
**  just got out of the pool with PID 4113 and exit code: 0
**  just got out of the pool with PID 4114 and exit code: 0
end

もう少し突っ込んでみる

use Parallel::ForkManager;
my $MAX_PROCESSES = 5;

my $pm = new Parallel::ForkManager($MAX_PROCESSES);

my $who_am_i = 'parent';
for my $i (1..10) {
    $pm->start and next;
    print "$i: $MAX_PROCESSES\n";
    $who_am_i = 'child';
    $pm->finish;
}
$pm->wait_all_children;
print "who_am_i: $who_am_i\n";
1: 5
2: 5
3: 5
4: 5
5: 5
6: 5
7: 5
8: 5
9: 5
10: 5
who_am_i: parent

$pm->startした時点で子プロセスが生成されるが、その時点で親プロセスの変数等を丸っとコピーする模様。当然プロセス間で変数は共有できないので、$who_am_iは'parent'になる。

さらに突っ込む

$pm->finishする前にdieするとどうなるか?

use Parallel::ForkManager;
my $MAX_PROCESSES = 5;

my $pm = new Parallel::ForkManager($MAX_PROCESSES);
for my $i (1..10) {
    $pm->start and next;
    die "child($i) die.\n";
    $pm->finish;
}
$pm->wait_all_children;
print "parent finish.\n";
child(1) die.
child(2) die.
child(3) die.
child(4) die.
child(5) die.
child(6) die.
child(7) die.
child(8) die.
child(9) die.
child(10) die.
parent finish.

子プロセスが死んでも、親は最後まで実行される。

さらにさらに突っ込む
use Parallel::ForkManager;
my $MAX_PROCESSES = 5;

my $pm = new Parallel::ForkManager($MAX_PROCESSES);
eval {
    for my $i (1..10) {

        $pm->start and next;
        die "child($i) die.\n";
        $pm->finish;
        print "never executed!";
    }

    $pm->wait_all_children;
    print "parent finish.\n";
};
if ($@) {
    print "trap\n";
}
trap
trap
trap
trap
trap
trap
trap
trap
trap
trap
parent finish.

子プロセス立ち上げた時点の親プロセスのメモリがコピーされると思えば、なるほどの挙動ではある。ただ、子プロセスでのエラーは子プロセスでevalして、必要に応じてexit -> run_on_finishが真っ当。