CakeFest 2024: The Official CakePHP Conference

pcntl_wait

(PHP 5, PHP 7, PHP 8)

pcntl_wait待つかフォークした子プロセスのステータスを返す

説明

pcntl_wait(int &$status, int $flags = 0, array &$resource_usage = []): int

この関数は、子プロセスが終了する・ カレントのプロセスを終了させるシグナルが送信される・シグナル処理関数を コールするシグナルが送信される のいずれかが発生するまでカレントのプロセスの実行を中断します。 子プロセスが、 コール時に 既に終了している場合("ゾンビ"プロセスと呼ばれます)、この関数は 直ちに処理を返します。子プロセスにより使用される全てのシステム リソースは、解放されます。waitpid のシステムでの動作に関する詳細は、 システムの wait(2) についての man ページを参照ください。

注意:

この関数は、process_id-1 を指定し、flags を何も設定せずに pcntl_waitpid() をコールするのと等価です。

パラメータ

status

pcntl_wait() は、パラメータ status の中にステータス情報を保存します。 このステータスは、次の関数を用いて評価可能です。 pcntl_wifexited()pcntl_wifstopped()pcntl_wifsignaled()pcntl_wexitstatus()pcntl_wtermsig() および pcntl_wstopsig()

flags

システム上で wait3 が使用可能な場合 (ほとんどの BSD 系システムが 該当します)、オプションのパラメータ flags を使用可能です。このパラメータが指定されない場合、wait はシステムコールに 対して使用されます。wait3 が使用できない場合、flags に値を設定しても何の影響も及ぼしません。 flags の値は、次の 2 つのグローバル定数の ゼロまたはそれ以上の論理和です。

flags のとりうる値
WNOHANG 子プロセスが終了していない場合に直ちに処理を返します。
WUNTRACED 停止した子プロセスの場合に処理を返します。 そして、ステータスは報告されません。

戻り値

pcntl_wait() は、終了した子プロセスの プロセス ID を返します。エラーの場合は -1、(wait3 が使用可能なシステムで) WNOHANG が使用され、子プロセスが利用できない場合に 0 を返します。

参考

  • pcntl_fork() - 現在実行中のプロセスをフォークする
  • pcntl_signal() - シグナルハンドラを設定する
  • pcntl_wifexited() - ステータスコードが正常終了を表しているかどうかを調べる
  • pcntl_wifstopped() - 子プロセスが現在停止しているかどうかを調べる
  • pcntl_wifsignaled() - ステータスコードがシグナルによる終了を表しているかどうかを調べる
  • pcntl_wexitstatus() - 終了した子プロセスのリターンコードを返す
  • pcntl_wtermsig() - 子プロセスを終了させたシグナルを返す
  • pcntl_wstopsig() - 子プロセスを停止させたシグナルを返す
  • pcntl_waitpid() - 待つかフォークした子プロセスのステータスを返す

add a note

User Contributed Notes 7 notes

up
5
federico at nextware dot it
18 years ago
This a simple multi process application where you can choose
the maximun process that can run at the same time.
This is useful when you need to limit the fork of process.
When the MAXPROCESS is reached the program wait on pcntl_wait()

<?php

DEFINE
(MAXPROCESS,25);

for (
$i=0;$i<100;$i++){
$pid = pcntl_fork();

if (
$pid == -1) {
die(
"could not fork");
} elseif (
$pid) {
echo
"I'm the Parent $i\n";
$execute++;
if (
$execute>=MAXPROCESS){
pcntl_wait($status);
$execute--;
}
} else {
echo
"I am the child, $i pid = $pid \n";
sleep(rand(1,3));
echo
"Bye Bye from $i\n";
exit;
}
}
?>
up
2
duerra at yahoo dot com
13 years ago
Using pcntl_fork() can be a little tricky in some situations. For fast jobs, a child can finish processing before the parent process has executed some code related to the launching of the process. The parent can receive a signal before it's ready to handle the child process' status. To handle this scenario, I add an id to a "queue" of processes in the signal handler that need to be cleaned up if the parent process is not yet ready to handle them.

<?php
declare(ticks=1);
//A very basic job daemon that you can extend to your needs.
class JobDaemon{

public
$maxProcesses = 25;
protected
$jobsStarted = 0;
protected
$currentJobs = array();
protected
$signalQueue=array();
protected
$parentPID;

public function
__construct(){
echo
"constructed \n";
$this->parentPID = getmypid();
pcntl_signal(SIGCHLD, array($this, "childSignalHandler"));
}

/**
* Run the Daemon
*/
public function run(){
echo
"Running \n";
for(
$i=0; $i<10000; $i++){
$jobID = rand(0,10000000000000);
$launched = $this->launchJob($jobID);
}

//Wait for child processes to finish before exiting here
while(count($this->currentJobs)){
echo
"Waiting for current jobs to finish... \n";
sleep(1);
}
}

/**
* Launch a job from the job queue
*/
protected function launchJob($jobID){
$pid = pcntl_fork();
if(
$pid == -1){
//Problem launching the job
error_log('Could not launch new job, exiting');
return
false;
}
else if (
$pid){
// Parent process
// Sometimes you can receive a signal to the childSignalHandler function before this code executes if
// the child script executes quickly enough!
//
$this->currentJobs[$pid] = $jobID;

// In the event that a signal for this pid was caught before we get here, it will be in our signalQueue array
// So let's go ahead and process it now as if we'd just received the signal
if(isset($this->signalQueue[$pid])){
echo
"found $pid in the signal queue, processing it now \n";
$this->childSignalHandler(SIGCHLD, $pid, $this->signalQueue[$pid]);
unset(
$this->signalQueue[$pid]);
}
}
else{
//Forked child, do your deeds....
$exitStatus = 0; //Error code if you need to or whatever
echo "Doing something fun in pid ".getmypid()."\n";
exit(
$exitStatus);
}
return
true;
}

public function
childSignalHandler($signo, $pid=null, $status=null){

//If no pid is provided, that means we're getting the signal from the system. Let's figure out
//which child process ended
if(!$pid){
$pid = pcntl_waitpid(-1, $status, WNOHANG);
}

//Make sure we get all of the exited children
while($pid > 0){
if(
$pid && isset($this->currentJobs[$pid])){
$exitCode = pcntl_wexitstatus($status);
if(
$exitCode != 0){
echo
"$pid exited with status ".$exitCode."\n";
}
unset(
$this->currentJobs[$pid]);
}
else if(
$pid){
//Oh no, our job has finished before this parent process could even note that it had been launched!
//Let's make note of it and handle it when the parent process is ready for it
echo "..... Adding $pid to the signal queue ..... \n";
$this->signalQueue[$pid] = $status;
}
$pid = pcntl_waitpid(-1, $status, WNOHANG);
}
return
true;
}
}
up
2
gaylord at 100days dot de
13 years ago
pcntl_wait will not terminate on signals if you have a PHP signal handler activated (pcntl_signal).
This is unless the signal handler was activated with 3rd parameter=true.

Example:
<?php
declare(ticks=1);
pcntl_signal(SIGTERM, "myHandler");
$pid=pcntl_wait($status);
?>

This will not terminate on SIGTERM sent to the process, because "wait" will be restarted after php recieves the signal. The signal handler "myHandler" will not be called unless pcntl_wait terminates for some other reason.

Change to:
<?php
declare(ticks=1);
pcntl_signal(SIGTERM, "myHandler", true);
$pid=pcntl_wait($status);
?>

Now the pcntl_wait terminates when a signal comes in and "myHandler" will be called on SIGTERM. (Make sure to put the wait in a loop though, because it will now not only terminate when a child exits but also when a signal arrives. Test for $pid>0 to detect a exit message from a child)
(thanks to Andrey for helping me debugging this)
up
0
duerra at yahoo dot com
13 years ago
Oops, I stripped just a little much from the job daemon code in the previous comment. You'll want to add a little line before the ->launchJob() method is called:

<?php

while(count($this->currentJobs) >= $this->maxProcesses){
echo
"Maximum children allowed, waiting...\n";
sleep(1);
}
up
0
digitalaudiorock at gmail dot com
13 years ago
I was unable to get pcntl_wait or pcntl_waitpid to terminate when I had an active signal handler. I then noticed the post below from gaylord at 100days dot de, however I'm a little confused by that post as I found the exact opposite to be true. The default value of the third parameter of pcntl_signal (the restart_syscalls parameter) is true and this seems to cause the wait to continue when the signal arrives. In order to prevent this I had to expressly set it to false. That is:

pcntl_signal(SIGTERM, 'my_handler_function', false);
up
0
thisisroot at gmail dot com
18 years ago
Below is a simple example of forking some children and timing the total duration (useful for stress tests).

<?php

$isParent
= true;
$children = array();
$start = microtime( true);

/* Fork you!
* (Sorry, I had to)
*/
$ceiling = $CONCURRENCY - 1;

for (
$i = 0; (( $i < $ceiling) && ( $isParent)); $i++) {
$pid = pcntl_fork();
if (
$pid === 0) {
$isParent = false;

} elseif (
$pid != -1) {
$children[] = $pid;

}

}

/* Process body */
echo "Do stuff here\n";

/* Cleanup */
if ( $isParent) {
$status = null;
while (
count( $children)) {
pcntl_wait( $status);
array_pop( $children);
}

echo
"Completed in " . ( microtime( true) - $start) . " seconds.\n";

}

?>
up
-1
thomas dot nicolai at unisg dot ch
18 years ago
The code before isnt working for me cause the children are correctly started but not refreshed after they died. So keep in mind to use this instead and use the signal handler to know when a child exits to know when you have to start a new one. I added a few lines to the posting from {andy at cbeyond dot net} cause his post wasnt working for me as well (PHP5.1). Same effect like the one below.

<?php
declare(ticks = 1);

$max=5;
$child=0;

// function for signal handler
function sig_handler($signo) {
global
$child;
switch (
$signo) {
case
SIGCHLD:
echo
"SIGCHLD received\n";
$child--;
}
}

// install signal handler for dead kids
pcntl_signal(SIGCHLD, "sig_handler");

while (
1){
$child++;
$pid=pcntl_fork();

if (
$pid == -1) {
die(
"could not fork");
} else if (
$pid) {

// we are the parent
if ( $child >= $max ){
pcntl_wait($status);
$child++;
}
} else {
// we are the child
echo "\t Starting new child | now we de have $child child processes\n";
// presumably doing something interesting
sleep(rand(3,5));
exit;
}
}
?>
To Top