F@N Ad-Tech Blog

株式会社ファンコミュニケーションズ nend・nex8のエンジニア・技術ブログ

PHPでもforkがしたい!

Introduction

 はじめまして、ADN事業部所属エンジニアのk_oomoriと申します。業務ではアドネットワークサービスnend(ネンド)においてPHPやJavaScriptのプログラミングからサーバソフトウェアあたりまでの領域を担当させていただいております。

 さて最初の投稿となる今回は、PHPにおけるプロセスのフォーク(fork)を扱おうと思います。
 フォークとは、簡単に言うと最初に起動したプロセス(以下親プロセスと言います)が自身の複製として子プロセスを生成し、複数プロセスで並列処理を行うというものです。
 Perlなどでは結構よく使われているようですが、PHPではあまり聞いたことがなかったので、この機会に紹介したいと思います。

!!!注意!!!
PHPでfork等のプロセス制御機能を利用するためには、マニュアルに明記されているとおり、configureオプションを指定してPHPをコンパイルしておかなければなりません。

PHPがサポートするプロセス制御機能は、デフォルトでは有効となっていません。プロセス制御機能を有効にするには、configureのオプションに--enable-pcntlを付け、CGI版あるいはCLI版のPHPをコンパイルする必要があります。(http://php.net/manual/ja/pcntl.installation.php)

後付で本番投入する場合は当該サーバを一旦サービスアウトするかメンテナンスを入れなくてはいけませんね・・・。

A Sample Code

 フォークを利用した実際のソースコードは以下のような流れになります。

$pids = array();
for ($i=0; $i<$loop_number; $i++) {
    $pid = pcntl_fork(); //・・・(1)
    if ($pid == -1) {
        writeErrorLog('プロセスのフォークに失敗しました。');
        break;
    } else if ($pid) {
        // 親プロセスの場合
        $pids[] = $pid; //・・・(2)
    } else {
        // 子プロセスの場合
        someChildProcess($i); //・・・(3)
        exit; //・・・(4)
    }
}
foreach ($pids as $pid) {
    pcntl_waitpid($pid, $status); //・・・(5)
}

 (1)pcntl_fork()をコールした段階で子プロセスが生成され、その返り値として子プロセスには0が、親プロセスには生成した子プロセスのプロセスIDが渡されます。(子プロセスの生成に失敗した場合は親プロセスに-1が返されます) ここではforでループしているため、$loop_number個の子プロセスができます。

 子プロセスはそれぞれに与えられた実処理を実行します(3)。ここでは自分が何番目の子なのかを判断するためループカウンタ$iをコールする関数に渡しています。
 基本的に子プロセスは目的の処理を終えたらその段階で終了させます(4)

 親プロセスが子プロセスの終了を判断するには、pcntl_waitpid()を使います(5)
 第1引数に子プロセスのPIDを渡すと、そのプロセスが終了するまで親は待ち状態になります。そのため(2)で子プロセスのPIDを収集しておきます。
 第2引数は参照渡しで、終了ステータスが返されます。(その値の評価はここでは省略しています)

 プロセスをフォークする際、データベース接続には注意しなければなりません。親プロセスでDB接続をオープンしたままの状態で子プロセスを生成してしまうと、この参照がプロセス間で共有されてしまい、1つの子プロセスが終了した段階で他の全てのプロセスからはDB接続がクローズされてしまったように見えます。これを避けるためには、フォーク後に各子プロセスが自分専用のDB接続をオープンするとよいでしょう。ただし、フレームワークを使用している場合、これは思った以上に困難だったりします。この場合、DBの実操作はAPIに押し込み、子プロセスは単にAPIを叩くだけという実装にするといった方法が考えられます。

Summary

 In this article I introduced pcntl_fork() as one of the process control functionalities of PHP. Calling this function creates a new child process from the parent one. It should be noted, however, that PHP must be compiled with "--enable-pcntl" option in order to enjoy the benefit of it.
 I showed a typical sample code, and described how the parent process could keep track of the progress of child processes.
 If you want the child processes to handle database connection, make sure to open it AFTER forking: If the parent process opened a database connection and then fork child processes without closing it, the children unintentionally share the connection and you would be faced with "lost connection" after the first child exits...

Reference(参考にさせていただいたサイト)