シナリオ:毎日のタスクで、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を返すことができます。
/ **この関数はSHELLでのみ実行できることを確認してください* / if(substr(php_sapi_name()、0、3)!== 'cli'){ die( "CLIモードのみ"); }
#!/ 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; 出口; } }
<?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 関数_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を返します。 }
posix_kill(posix_getpid(), SIGHUP); 为自己生成SIGHUP信号
declare(ticks = 1); //php < 5.3
pcntl_signal()
機能だけで登録した信号とその処理方法、実際の受信信号とその処理方法を呼び出すをされ
pcntl_signal_dispatch()
、新たな信号待ち派遣するかどうかを検出するために、サイクル内の関数呼び出さなければなりません。
pcntl_signal_dispatch()
declare(ticks = 1);
つまり、低レベルの命令が実行されるたびにシグナルがチェックされ、登録されたシグナルが検出されると、シグナルプロセッサが呼び出されます。
kill [PID]
コマンドは、任意の他のパラメータを追加しませんでした、プログラムはSIGTERMシグナルを受信します。
<?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(); } }
<?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()); } 出口; } }
(1)親プロセスは、waitやwaitpidなどの関数を介して子プロセスの終了を待ちます。これにより、親プロセスがハングします。子プロセスを長時間実行する必要がある場合(タイムアウトが発生する場合)には適していません。
wait()またはwaitpid()システムコールを実行すると、子プロセスは終了後すぐにプロセステーブルのデータを親プロセスに返し、システムはすぐにエントリポイントを削除します。この場合、機能しなくなったプロセスはありません。
(2)親プロセスがビジーの場合、シグナル関数を使用してSIGCHLDのハンドラーをインストールできます。子プロセスが終了すると、親プロセスがシグナルを受信し、ハンドラーでwaitを呼び出してそれをリサイクルできます。
(3)子プロセスの終了時に親プロセスが気にしない場合は、シグナル(SIGCLD、SIG_IGN)またはシグナル(SIGCHLD、SIG_IGN)を使用して、子プロセスの終了に興味がないことをカーネルに通知できます。その後、子プロセスが終了した後、カーネルはリサイクルして、親プロセスにシグナルを送信しない
(4)2回フォークし、親プロセスは子プロセスをフォークしてから引き続き動作します。子プロセスは孫プロセスをフォークして終了し、孫プロセスはinitに引き継がれます。孫プロセスが終了すると、initはリサイクルされます。ただし、子プロセスのリサイクルは自分で行う必要があります。
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)