PHPマルチプロセスの概要

マルチプロセス-フォーク

シナリオ:毎日のタスクで、phpスクリプトを使用してログ分析、キュー処理、その他のタスクを実行する必要がある場合があります。データの量が多い場合は、複数のプロセスを使用して処理できます。

準備:phpマルチプロセスにはpcntl、posix拡張サポートが必要で、php-mで表示できます。インストールされていない場合は、phpとパラメーターを再コンパイルする必要があります。--enable-pcntl,posix一般默认会有。

注:

    マルチプロセスの実装は、CLIモードでのみ可能です。Webサーバー環境では、予期しない結果が発生します。テストします。报错:Call to undefined function: pcntl_fork()

   「errno = 32 Broken pipe」を引き起こすエラーpcntl_fork#474、https://github.com/phpredis/phpredis/issues/474を参照

        2つの点に注意してください。ループで子プロセスを作成する場合、子プロセスがループに入らないようにするには、子プロセスを最後に終了する必要があります。
                      子プロセスで開いている接続はコピーできず、メインプロセスが使用されます。複数ケースモードが必要です。

pcntl_fork:

  1回の呼び出しで2回戻り、子プロセスpidが親プロセスで返され、0が子プロセスで返され、エラーの場合は-1が返されます。

 pcntl_wait(int&$ status [、int $ options]):

    子プロセスが終了するか、現在のプロセスを終了するためのシグナルを受信するまで、現在のプロセスをブロックします。これは、現在のプロセスを終了するためのシグナルであることに注意してください。子プロセスによって送信されるSIGCHLDはカウントされません。$ statusを使用して子プロセスのステータスコードを返します。2番目のパラメーターを指定して、ブロッキング状態で呼び出すかどうかを指定できます。

        ブロッキングモードで呼び出された場合、関数の戻り値は子プロセスのpidです。子プロセスがない場合、戻り値は-1です。

        非ブロッキング方式で呼び出され、この関数は、実行されているが終了していない子プロセスがある場合にも0を返すことができます。

pcntl_waitpid(int $ pid、int&$ status [、int $ options])
         関数はpcntl_waitと同じですが、waitpidが指定されたpidを待機する子プロセスである点が異なります。pidが-1の場合、pcntl_waitpidはpcntl_waitと同じです。子プロセスのステータス情報は、2つの関数pcntl_waitおよびpcntl_waitpidの$ステータスに格納されます。
 
CLIモードかどうかを確認する
/ **この関数はSHELLでのみ実行できることを確認してください* /
if(substr(php_sapi_name()、0、3)!== 'cli'){
  die( "CLIモードのみ");
}

 

SHELLスクリプトはマルチプロセスを実装します(これは、Qbusマルチプロセスが実装されている方法です。ただし、nohupと&はデーモンとして使用されます)。
コードをコピー
#!/ bin / bash
 
for((i = 1; i <= 8; i ++))
行う    
    / usr / local / bin / php multiprocessTest.php&
終わった
 
待つ
コードをコピー

上記のシェルプログラムは、forループを使用した非常に単純なマルチプロセスプログラムをリストし、同時に8つのプロセスを達成して、multiprogramTest.phpこのプログラムを実行します。最後の待機ステートメントは、メインプロセスがすべてのプロセスが実行されるのを待ってから実行を停止することもできます。

このプログラムは問題なく、多くの既存のコードもこの方法で実装されていますが、このプログラムの同時実行プログラムの数は制御できません。つまり、マシンのコアの数に応じて各プロセスの切り替えをスケジュールできません。

私たちのマシンに8コア以上ある場合、上記のプログラムは問題なく、すべてのコアを十分に活用でき、リソース間の競争はありません。

しかし、マシンに8コアがなく、同時に実行されているプロセスの数がコアの数よりも多い場合、システムにはプロセスの割り当てとスケジューリングの問題があり、それに応じてリソースの競合も発生します。継続的に実行すると、すべてのプロセスがシステムのスケジューリングに従い、より不確実な要素が現れます。

常に一定数の子プロセスを実行する例
コードをコピー
<?php

//子プロセスの最大数
$ maxChildPro = 8;

//子プロセスの現在の数
$ curChildPro = 0;

//この関数は、子プロセスが終了するとトリガーされます。現在の子プロセスの数-1
関数sig_handler($ sig)
{
    グローバル$ curChildPro;
    スイッチ($ sig){
        ケースSIGCHLD:
            echo 'SIGCHLD'、PHP_EOL;
            $ curChildPro--;
            ブレーク;
    }
}

// pcntl_signalと一緒に使用します。簡単に言うと、システムがタイムクラウドを生成して、シグナルキャプチャ機能がセマフォをキャプチャできるようにします。
declare(ticks = 1);

//子プロセスの終了時に呼び出される関数を登録します。SIGCHLD:プロセスが終了または停止すると、SIGCHLDシグナルを親プロセスに送信します。
pcntl_signal(SIGCHLD、 "sig_handler");

while(true){
    $ curChildPro ++;
    $ pid = pcntl_fork();
    if($ pid){
//親プロセスはコードを実行します。上限に達すると、親プロセスはブロックし、子プロセスが終了するのを待ち、ループが続行します
        if($ curChildPro> = $ maxChildPro){
            pcntl_wait($ status);
        }
    } そうしないと {
//コードを実行する子プロセス
        $ s = rand(2、6);
        スリープ($ s);
        echo "子の睡眠$ s 2番目の終了"、PHP_EOL;
        出口;
    }
}
コードをコピー

 

waitpid関数を使用してすべての子プロセスが終了するのを待ち、ゾンビプロセスを防止する例
コードをコピー
<?php

$ childs = array();

// Fork10の子プロセス
for($ i = 0; $ i <10; $ i ++){
    $ pid = pcntl_fork();
    if($ pid == -1)
        die( 'フォークできなかった');

    if($ pid){
        エコー "親\ n";
        $ childs [] = $ pid;
    } そうしないと {
// $ i + 1(s)をスリープします。子プロセスは$ iパラメータを取得できます
        sleep($ i + 1);

//子プロセスがforループに入らないようにするには、子プロセスを終了する必要があります
        出口();
    }
}

while(count($ childs)> 0){
    foreach($ childs as $ key => $ pid){
        $ res = pcntl_waitpid($ pid、$ status、WNOHANG);

//-1はエラーを意味し、0より大きい場合は子プロセスが終了し、子プロセスのpidが返され、0は非ブロッキングのときに子プロセスが終了していないことを意味します
        if($ res == -1 || $ res> 0)
            unset($ childs [$ key]);
    }

    スリープ(1);
}
コードをコピー

 

実用的な例として、phpは同時ログコピーを実装します
コードをコピー
<?php
関数_fetchLog()
{
    $ password = $ this-> _ getPassword();
    $ online_log_path = NginxConf :: getArchiveDir($ this-> _ stat_day);
    $ task_log_path = QFrameConfig :: getConfig( 'LOG_PATH');
    $ children = array();
    $ success = true;
    foreach($ this-> _ server_list as $ host => $ value)
    {
        $ local_dir = $ this-> _ prepareLocalDir($ host);
        $ task_log = "$ task_log_path / fetch_log。$ host";
        $ cmd = "sshpass -p $ password rsync -av -e 'ssh -o StrictHostKeyChecking = no' $ host:$ online_log_path / * $ local_dir >> $ task_log 2>&1";
        $ pid = pcntl_fork();
        if(-1 === $ pid)
        {
            LogSvc :: log( 'stat_pv_by_citycode_error'、 'フォークできませんでした');
            exit( 'フォークできませんでした');
        }
        else if(0 === $ pid)
        {
            system($ cmd、$ return_value);
            if(0!== $ return_value)
            {
                LogSvc :: log( 'stat_pv_by_citycode_error'、 "rsync $ host error");
            }
            exit($ return_value);
        }
        そうしないと
        {
            $ children [$ pid] = 1;
        }
    }
    while(!empty($ children))
    {
        $ pid = pcntl_waitpid(-1、$ status、WNOHANG);
        if(0 === $ pid)
        {
            スリープ(1);
        }
        そうしないと
        {
            if(0!== pcntl_wexitstatus($ status))
            {
                $ success = false;
            }
            unset($ children [$ pid]);
        }
    }
    $ successを返します。
コードをコピー

 
マルチプロセス信号
 
同じタイプの信号を1つだけ保存でき、超過分は自動的に破棄されます
0未満の値を送信すると、実際にはプロセスのグループである自分自身を含むすべての子プロセスに送信できます。
 
PCNTLは、ティックをシグナルハンドルコールバックメカニズムとして使用して、非同期イベントの処理の負荷を最小限に抑えます。ダニとは何ですか?Tickは、インタープリターがコードセグメントでN個の低レベルステートメントを実行するたびに発生するイベントです。このコードセグメントは、declareで指定する必要があります。
 
    pcntl_signal(int $ signo、callback $ handler [、bool $ restart_syscalls])// SIGのハンドラーを登録
      posix_kill(posix_getpid(), SIGHUP); 为自己生成SIGHUP信号
       declare(ticks = 1); //php < 5.3
      pcntl_signal_dispatch  (void)  
pcntl_signal()を         介してシグナルが インストールされるのを待つ各プロセッサを呼び出します 説明: pcntl_signal() 機能だけで登録した信号とその処理方法、実際の受信信号とその処理方法を呼び出すをされ pcntl_signal_dispatch() 、新たな信号待ち派遣するかどうかを検出するために、サイクル内の関数呼び出さなければなりません。
      pcntl_signal_dispatch()
      この関数はPHP 5.3以降でのみサポートされています。PHPのバージョンが5.3以降の場合は、このメソッドを使用してシグナルプロセッサを呼び出すことをお勧めします。5.3より前のバージョンでは、シグナルを登録する前に文を追加する必要があります。 declare(ticks = 1); つまり、低レベルの命令が実行されるたびにシグナルがチェックされ、登録されたシグナルが検出されると、シグナルプロセッサが呼び出されます。
     pcntl_alarm(int $ seconds)
        $秒後にSIGALRMシグナルを送信するようにカウンターを設定する
                        
                        
5.信号を送信します。
 
    posix_kill():プロセスにシグナルを送信します。 
    SIGINT:キーボードCTRL + Cを使用。
    SIGTERM:時々応答しないプロセスが実行される kill [PID] コマンドは、任意の他のパラメータを追加しませんでした、プログラムはSIGTERMシグナルを受信します。        
           プログラムが上記の2つのシグナルを受信すると、デフォルトで実行が終了し、シグナルを登録することでデフォルトの動作を変更できます。
 
シグナルプロセッサを登録する例は結論を引き出します:
スリープ機能は信号によって起こされ、もうスリープせず、ウェイクアップしたときに残っている秒数を返します
同じ種類の信号の場合、保存できるのは1つだけで、超過分は自動的に破棄されます。テスト後、CTRL + Cを複数回押すと、プロセスが最初にスリープをウェイクアップし、次にusleepをウェイクアップします。pcntl_signal_dispatchを実行すると、一度に複数の「SIGINT」が出力されます。店1つだけではありません。
コードをコピー
<?php

// SIGINTシグナルを受信した後、1行の情報のみを出力するプロセッサを定義します
function signalHandler($ signal)
{
    if($ signal == SIGINT){
        echo 'SIGINT'、PHP_EOL;
    }
}

//シグナルの登録:SIGINTシグナルを受け取ったら、signalHandler()関数を呼び出します
pcntl_signal(SIGINT、 'signalHandler');

/ **
 * PHP <5.3使用
 * pcntl_signalと組み合わせて使用​​されます。これは、低レベルの命令が実行されるたびに信号が1回チェックされ、登録された信号が検出されると、その信号プロセッサが呼び出されることを意味します。
 * /
if(!function_exists( "pcntl_signal_dispatch")){
    declare(ticks = 1);
}

while(true){
    $ s = sleep(10);
    echo $ s、PHP_EOL; //シグナルはスリープを起動し、残りの秒数を返します。

//何かをする
    ($ i = 0; $ i <5; $ i ++){
        $ iをエコーし​​ます。PHP_EOL;
        usleep(100000);
    }

    / **
     * PHP> = 5.3
     *インストールされている信号プロセッサを呼び出す
     *ディスパッチを待機している新しいシグナルがあるかどうかを検出するために、ループで呼び出す必要があります。
     * /
    if(!function_exists( "pcntl_signal_dispatch")){
        pcntl_signal_dispatch();
    }

}
コードをコピー

 

pcntl_alarmを介して5秒ごとに信号を送信する例
コードをコピー
<?php

declare(ticks = 1);

function signal_handler($ signal){
    "Caught SIGALRM \ n"を出力します。
    pcntl_alarm(5);
}

pcntl_signal(SIGALRM、 "signal_handler"、true);
pcntl_alarm(5);

ために(;;) {
}
コードをコピー

 

シグナルを送信してプロセスを強制終了する例シグナルは、自分自身または他のプロセスに送信できます。
コードをコピー
<?php

/ **
 *親プロセスは子プロセスがpcntl_waitを介して終了するのを待ちます
 *子プロセスはシグナルを介して自分自身を強制終了するか、親プロセスでkilシグナルを送信して子プロセスを終了できます
 * /

//子プロセスを生成します
$ pid = pcntl_fork();
if($ pid == -1){
    die( 'フォークできませんでした');
}そうしないと{
    if($ pid){
        $ status = 0;
//子プロセスが終了するまで親プロセスをブロックします。長時間実行する必要があるスクリプトには適していません。
        // pcntl_wait($ status、WNOHANG)を使用して非ブロッキングを実現できます
        pcntl_wait($ status);
        出口;
    }そうしないと{
//ゾンビプロセスの生成を防ぐために、現在の子プロセスを終了します
        if(function_exists( "posix_kill")){
            posix_kill(getmypid()、SIGTERM);
        }そうしないと{
            system( 'kill -9'。getmypid());
        }
        出口;
    }
}
コードをコピー

 
マルチプロセスゾンビプロセス
 
子プロセスが親プロセスの前に終了するが、親プロセスはまだ実行中であり、長時間終了しない場合、子プロセスはゾンビプロセスになります。次に、カーネルはゾンビプロセスのPPIDを検出し、SIGCHLDシグナルをPPIDに送信します。親プロセスがpcntl_waitまたはpcntl_waitpid関数を渡さず、posix_killを介して子プロセスに終了信号を送信しない場合、子プロセスは完全にゾンビです。
                ps auxを表示したときのZ(ゾンビ)ステータス。
                通常、親プロセスが終了する前に子プロセスをリサイクルする必要がありますpcntl_wait()関数は、子プロセスが終了するまで親プロセスを一時停止します。
                親プロセスが最初にハングした場合、子プロセスはプロセス1に引き継がれ、子プロセスが終了すると、プロセス1は自動的に回復されます。したがって、ゾンビプロセスをシャットダウンする別の方法は、親プロセスをシャットダウンすることです。
                子プロセスが親プロセスが終了したことを知る方法:
                                1.親プロセスが終了すると、子プロセスを採用するためのINITプロセスがあります。このINITプロセスのプロセス番号は1なので、子プロセスはgetppid()を使用して現在の親プロセスのpidを取得できます。1が返された場合、親プロセスがINITプロセスになり、元のプロセスが起動されたことを示します。
 
                                2. kill関数、phpのposix_killを使用して、空のシグナル(kill(pid、0))を元の親プロセスに送信します。このメソッドを使用して、実際にシグナルを送信せずにプロセスの存在を確認します。したがって、この関数が-1を返す場合、親プロセスは終了しています。
 
                ゾンビプロセス:
                                UNIXシステムでは、プロセスは終了しますが、彼の親プロセスは彼を待機せず(call wait / waitpid)、ゾンビプロセスになります。
                        ゾンビプロセスは長い間使用されていないプロセスですが、それでもプロセステーブルのスロットを占有します。
                        ゾンビプロセスが時間内に回復されない場合、システムのプロセスエントリが占有されます。このようなゾンビプロセスが多すぎる場合、システムに使用可能なプロセスエントリがないため、他のプログラムを実行できません。
                                プロセスが終了する前(終了出口)は、ゾンビプロセスになります。プロセスの状態などの情報を保存するために使用されます。なんで?
                                子プロセスの終了と親プロセスの実行は非同期プロセスです。つまり、親プロセスは子プロセスがいつ終了するか予測できません。次に、親プロセスがビジー状態で子プロセスを待機できないため、または子プロセスがいつ終了するかわからない場合、子プロセスの最後の状態情報は失われますか?いや UNIXは、親プロセスが子プロセスの最後にステータス情報を知りたい限り、それを取得できるメカニズムを提供するため、取得できます。このメカニズムは次のとおりです。子プロセスが独自のライフサイクルを完了すると、exit()システムコールが実行され、カーネルはプロセスのすべてのリソース(開いているファイル、占有されているメモリなど)を解放します。ただし、特定の情報(プロセスID、プロセスID、終了コード、終了コード、終了ステータス、プロセスの終了ステータス、実行時間、プロセスで使用されたCPU時間など)は保持されますが、これらのデータはシステムまで保持されますwait / waitpidを介して親プロセスによって解放されるまで、それを親プロセスに渡します。
                                
                                ゾンビのプロセスを防ぐ
                                                

(1)親プロセスは、waitやwaitpidなどの関数を介して子プロセスの終了を待ちます。これにより、親プロセスがハングします。子プロセスを長時間実行する必要がある場合(タイムアウトが発生する場合)には適していません。      

    wait()またはwaitpid()システムコールを実行すると、子プロセスは終了後すぐにプロセステーブルのデータを親プロセスに返し、システムはすぐにエントリポイントを削除します。この場合、機能しなくなったプロセスはありません。

    (2)親プロセスがビジーの場合、シグナル関数を使用してSIGCHLDのハンドラーをインストールできます。子プロセスが終了すると、親プロセスがシグナルを受信し、ハンドラーでwaitを呼び出してそれをリサイクルできます。

    (3)子プロセスの終了時に親プロセスが気にしない場合は、シグナル(SIGCLD、SIG_IGN)またはシグナル(SIGCHLD、SIG_IGN)を使用して、子プロセスの終了に興味がないことをカーネルに通知できます。その後、子プロセスが終了した後、カーネルはリサイクルして、親プロセスにシグナルを送信しない

    (4)2回フォークし、親プロセスは子プロセスをフォークしてから引き続き動作します。子プロセスは孫プロセスをフォークして終了し、孫プロセスはinitに引き継がれます。孫プロセスが終了すると、initはリサイクルされます。ただし、子プロセスのリサイクルは自分で行う必要があります。

                    
                    ゾンビプロセスの表示とクリア:テストにより、ps -efとauxがゾンビプロセスを認識できないことがわかりました。
                    topコマンドを使用して、現在のシステムのゾンビプロセスの数をリアルタイムで確認する必要があります。
                
 
                psコマンドを使用して、ゾンビプロセスを表示します。
                
            ps -A -ostat、ppid、pid、cmd | grep -e '^ [Zz]'
 
   
           コマンドの注釈:
  -Aパラメータはすべてのプロセスをリストします
  -oカスタム出力フィールド表示フィールドをstat(ステータス)、ppid(プロセスの親ID)、pid(プロセスID)、cmd(コマンド)これらの4つのパラメーターに設定します
ステータスがzまたはZのプロセスはゾンビプロセスであるため、grepを使用してステータスをzZプロセスとして取得します。
 
   
        結果は次のとおりです。
    現時点では、 kill -HUP 5255 杀掉这个进程。如果再次查看僵尸进程还存在,可以kill -HUP 5253(父进程)。
    如果有多个僵尸进程,可以通过
    ps -A -ostat、ppid、pid、cmd | grep -e '^ [Zz]' | awk 'print {$ 2}' | xargs kill -9
    対処します。



マルチプロセス間プロセス通信(IPC)
 
パイプは、プロセス間の通信データを運ぶために使用されます。理解を容易にするために、パイプラインをファイルと比較し、プロセスAがパイプラインPにデータを書き込み、次にプロセスBがパイプラインPからデータを読み取ることができます。phpが提供するパイプライン操作APIは、パイプラインの作成でposix_mkfifo関数を使用し、読み取り操作と書き込み操作がファイル操作関数と同じであることを除いて、基本的にファイル操作APIと同じです。もちろん、ファイルを直接使用してパイプラインをシミュレートすることはできますが、パイプラインの特性を使用することはできません。

おすすめ

転載: www.cnblogs.com/liliuguang/p/12697002.html