NAME

perlfork - Perl の fork エミュレーション

SYNOPSIS

    注意: 5.8.0 のリリースと共に、fork() エミュレーションはかなり成熟し
    ています。しかしながら、またいくつかのバグや実際の fork() との差違
    が知られています。後述の "バグ" 及び "警告と制限" の章も参照してく
    ださい。

Perl は同名の Unix システムコールに対応するキーワード fork() を 提供しています。 fork() システムコールが存在する大抵の Unix 風プラットフォームでは Perl の fork() は単純にそれを呼ぶだけです。

Windows といった fork() システムコールを持っていないいくつかの プラットフォームでは、インタプリタレベルで fork() のエミュレーションを 構築します。 エミュレーションは Perl プログラムのレベルに於いて 本物の fork() とできる限り互換がとれるように設計されていますが, この方法で生成される全ての仮想的な子「プロセス」は オペレーティングシステムが関与する限りでは同じ実プロセスとして 存在するためにいくらかの重要な違いが存在します。

このドキュメントでは fork() エミュレーションの能力と限界の概要を 提供します。 ここで述べられていることは本物の fork() が存在して Perl がそれを使うように設定されているプラットフォームには 当てはまりません。

DESCRIPTION

fork() エミュレーションは Perl インタプリタのレベルで実装されて います。 これの意味するところはおおざっぱに言うと fork() の実行は実際には 実行しているインタプリタとその状態の全てを複製し、 複製されたインタプリタを別のスレッドで、親で fork() が呼び出された すぐ後から実行を始めることです。 仮想的なプロセスとしてこの子「プロセス」を実装しているスレッドに 着目します。

fork() を呼び出した Perl プログラムにとって、この全ては透過的であるように 設計されています。 親プロセスは fork() からその後の プロセス操作関数で使うことのできる仮想プロセス ID を伴って戻り、 子プロセスでは子仮想プロセスであることを示す値 0 を伴って 戻ります。

fork された擬似プロセスの中でのその他の Perl の機能の振る舞い

大抵の Perl の機能は仮想プロセスでも自然に振る舞います。

$$ 及び $PROCESS_ID

この特殊変数は適切に仮想プロセス ID に設定されます。 これは特定のセッションに於いて仮想プロセスを識別するために 使うことができます。 この値は wait() された後に起動された 仮想プロセスでは再利用されることに注意して下さい。

%ENV

各仮想プロセスはそれぞれの仮想環境を持っています。 %ENV の変更は仮想環境に作用し、その仮想プロセスと、そこから起動した 全てのプロセス(及び仮想プロセス)でのみ見ることができます。

chdir() 及びファイル名を受け取る他の全ての組み込み関数

各仮想プロセスはそれぞれに仮想的なカレントディレクトリの主題(idea)を 持っています。 chdir() を使ったカレントディレクトリの変更はその 仮想プロセスと、そこから起動した全てのプロセス(及び仮想プロセス)でのみ 見ることができます。 仮想プロセスからの全てのファイル及びディレクトリアクセスは 仮想作業ディレクトリから実作業ディレクトリへと適切に 正しく変換されます。

wait() 及び waitpid()

wait() 及び waitpid() に fork() から返される仮想プロセス ID を 渡すことができます。 これらの呼び出しは仮想プロセスの終了を適切に待ち、 その状態を返します。

kill()

kill() は fork() から返された ID を渡すことで仮想プロセスを 停止することができます。 しかしこれは悲惨な状況下以外では使うべきではありません、 なぜならオペレーティングシステムは実行しているスレッドが終了した 時ではプロセスリソースの完全性を保証しないかもしれないからです。 仮想プロセスに対して kill() を使うと大抵メモリリークを引き起こします; これは仮想プロセスを実装しているスレッドにはそのリソースを解放する タイミングをとれないためです。

exec()

仮想プロセスでの exec() 呼び出しは実際には要求された実行形式を 別のプロセス空間で呼び出し、そのプロセスの終了ステータスと 同じステータスで終了するように待機します。 これは実行している実行形式が持っているプロセス ID がそれに先立つ Perl の fork() で返されたプロセス ID は異なることを意味します。 同じように、fork() によって返された ID を渡すプロセス操作関数は exec() の後で待っている実プロセスではなく、exec() を呼び出した 仮想プロセスに対して作用します。

exec() が擬似プロセスの内側で呼び出されると、DESTROY メソッドと END ブロックは外部プロセスが返ってきた後に呼び出されるままです。

exit()

exit() はいつでも、起動中の子仮想プロセスを自動的に wait() してから、 実行している仮想プロセスを終了させます。 これはそのプロセスは全ての 実行中の仮想プロセスが終了するまでしばらくの間終了しないことを 意味します。 オープンしたファイルハンドルに関する制限については以下を参照してください。

ファイル、ディレクトリ、及びネットワークソケットに対する開いているハンドル

全ての開いているハンドルは子プロセスで dup() されるので、 どこかのプロセスでハンドルを閉じても他には影響しません。 いくつかの制限については続きを見て下さい。

リソースの制限

オペレーシングシステムから見ると、fork() エミュレーションから生成された 仮想プロセスは単なる同じプロセス内のスレッドです。 これはオペレーシングシステムによって科せられた全てのプロセスレベルの制限は 全ての仮想プロセスで一緒に割り当てられます。 これには開いているファイル、 ディレクトリ、ソケットの数の制限、ディスク使用量の制限、 メモリサイズの制限、CPU 使用量の制限等が含まれます。

親プロセスの kill

親プロセスが kill (Perl の kill() 組み込み関数若しくは外部の同等の 物で)されると、全ての仮想プロセスも同様に kill され、プロセス全体が 終了します。

親プロセスと仮想プロセスの生存期間

通常のイベントの進み方であれば、親プロセスとそこから起動された それぞれの仮想プロセスは終了する前に各自の仮想子プロセスを待つでしょう。 これは親プロセスとそこから起動されたそれぞれのそれがまた 仮想親プロセスである仮想子プロセスはそれらの仮想子プロセスが終了した 後でのみ終了するでしょう。

仮想プロセスがその親プロセスから detach して実行している (つまり親は必要がないのなら wait() する必要がない)とマークする方法は 今後提供されるでしょう。

警告及び制限

BEGIN ブロック

fork() エミュレーションは BEGIN ブロックで呼ばれた時には完全には 正しく動作しません。 fork された複製は BEGIN ブロックの内容を実行しますが、BEGIN ブロックの後の ソースストリームのパースを継続しません。 例えば、次のコードを考えてみます:

    BEGIN {
        fork and exit;          # fork child and exit the parent
        print "inner\n";
    }
    print "outer\n";

これは次のように出力します:

    inner

本来は次のようであるはずです:

    inner
    outer

この制限はパース途中の Perl パーサによって使われるスタックの 複製と再開における基礎技術の複雑さに起因しています。

開いているファイルハンドル

fork() した時点で開いている全てのファイルハンドルは dup() されます。 つまり、ファイルは親と子とで独立して閉じることができます; しかし dup() されたハンドルはまだ同じシークポインタを共有していることに 注意して下さい。 親でシーク位置を変更するとそれは子にも波及し、その逆も同様です。 これは子供と分離したシークポインタが必要なファイルを開くことで 無効にできます。

いくつかの OS (特に Solaris や Unixware) では、子プロセスから exit() が 呼び出されると、親のオープンしているファイルハンドルはフラッシュされて 閉じられるので、結果としてファイルハンドルが壊れます。 これらのシステムでは、代わりに _exit() を呼ぶべきです。 _exit()POSIX モジュールを通して Perl で利用可能です。 これに関するさらなる情報についてはシステムの man ページを参考にしてください。

pipe open() の fork はまだ実装されていません

open(FOO、"|-") 及び open(BAR、"-|") 構成子は実装されていません。 この制限は明示的にパイプを作る新しいコードで簡単に取り除けます。 以下の例で fork された子に書き出す方法を示します:

    # simulate open(FOO, "|-")
    sub pipe_to_fork ($) {
        my $parent = shift;
        pipe my $child, $parent or die;
        my $pid = fork();
        die "fork() failed: $!" unless defined $pid;
        if ($pid) {
            close $child;
        }
        else {
            close $parent;
            open(STDIN, "<&=" . fileno($child)) or die;
        }
        $pid;
    }

    if (pipe_to_fork('FOO')) {
        # parent
        print FOO "pipe_to_fork\n";
        close FOO;
    }
    else {
        # child
        while (<STDIN>) { print; }
        exit(0);
    }

そしてこちらは子から読む時です:

    # simulate open(FOO, "-|")
    sub pipe_from_fork ($) {
        my $parent = shift;
        pipe $parent, my $child or die;
        my $pid = fork();
        die "fork() failed: $!" unless defined $pid;
        if ($pid) {
            close $child;
        }
        else {
            close $parent;
            open(STDOUT, ">&=" . fileno($child)) or die;
        }
        $pid;
    }

    if (pipe_from_fork('BAR')) {
        # parent
        while (<BAR>) { print; }
        close BAR;
    }
    else {
        # child
        print "pipe_from_fork\n";
        exit(0);
    }

pipe open() の fork は今後実装されるでしょう。

XSUB の保持しているグローバル状態

それ自身でグローバル状態を保持している外部関数(XSUBs; external subroutines)は正しく動作しないでしょう。 そのような XSUB は、異なる仮想プロセスからグローバルデータに対して同時に アクセスするのを防ぐためのロックも保持するが、その全ての状態を fork() 時に自然と複製される Perl シンボルテーブル上に置くかする 必要があるでしょう。 拡張に対して複製するタイミングを提供するコールバック機構は近い将来 提供されるでしょう。

大きなアプリケーションに埋め込まれているインタプリタ

fork() エミュレーションは Perl インタプリタを埋め込んでいて Perl コードを評価(eval)する Perl API を少しだけ呼び出すような アプリケーションの内部で実行されている時には、予期したように 振る舞わないかもしれません。 これは、エミュレーションは Perl インタプリタ自身の持っているデータ構造しか 知らず、格納しているアプリケーションの 状態に関しては何も知らないために生じます。 例えば、アプリケーションの自分のコールスタックで継続している状態は 手の届かないところにあります。

エクステンションのスレッド安全性

fork() エミュレーションはコードを複数のスレッドで実行するために、 スレッドセーフでないライブラリを呼び出すエクステンションは fork() を 呼び出すと正しく動作しないかもしれません。 Perl のスレッドサポートは徐々にネイティブな fork() を持っている プラットフォームにも広く導入されてきているので、そのようなエクステンションは スレッドセーフに修正するように期待されています。

BUGS

AUTHOR

並列インタプリタと fork() エミュレーションのサポートは Microsoft Corporation の資金援助で ActiveState によって実装されました。

このドキュメントは Gurusamy Sarathy <gsar@activestate.com> によって書かれ、メンテナンスされています。

SEE ALSO

"fork" in perlfunc, perlipc