pcntl_wait

(PHP 5, PHP 7, PHP 8)

pcntl_wait等待或返回分叉子进程的状态

描述

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

wait 函数挂起当前进程的执行,直到子进程退出,或者直到传递一个信号,其操作是终止当前进程或调用信号处理函数。如果子进程在调用时已经退出(所谓的“僵尸”进程),则该函数立即返回。子进程使用的任何系统资源都将被释放。有关 wait 在您的系统上的工作方式的具体细节,请参阅您系统的 wait(2) 手册页。

注意:

此函数等效于调用 pcntl_waitpid(),其 -1 process_id 和没有 flags

参数

status

pcntl_wait() 将在 status 参数中存储状态信息,可以使用以下函数对其进行评估:pcntl_wifexited()pcntl_wifstopped()pcntl_wifsignaled()pcntl_wexitstatus()pcntl_wtermsig()pcntl_wstopsig()

flags

如果您的系统上可用 wait3(主要是 BSD 风格的系统),您可以提供可选的 flags 参数。如果没有提供此参数,wait 将用于系统调用。如果 wait3 不可用,为 flags 提供值将没有任何效果。 flags 的值是以下两个常量中一个或多个常量的值 OR 运算的结果

flags 的可能值
WNOHANG 如果没有任何子进程退出,则立即返回。
WUNTRACED 返回被停止的子进程,其状态尚未报告。

返回值

pcntl_wait() 返回退出的子进程的进程 ID,错误时返回 -1,或者如果提供 WNOHANG 作为选项(在 wait3 可用的系统上)并且没有子进程可用时返回零。

参见

添加注释

用户贡献的注释 7 个注释

6
federico at nextware dot it
18 年前
这是一个简单的多进程应用程序,您可以在其中选择
同时运行的最大进程数。
这在您需要限制进程分叉时很有用。
当达到 MAXPROCESS 时,程序将在 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;
}
}
?>
3
duerra at yahoo dot com
14 年前
在某些情况下使用 pcntl_fork() 可能有点棘手。对于快速作业,子进程可能在父进程执行与启动进程相关的某些代码之前完成处理。父进程可能会在准备好处理子进程状态之前收到信号。为了处理这种情况,我在信号处理程序中向需要清理的进程“队列”中添加了一个 ID,如果父进程尚未准备好处理它们,则需要清理它们。

<?php
declare(ticks=1);
// 一个非常基本的作业守护进程,您可以根据需要扩展它。
class JobDaemon{

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

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

/**
* 运行守护进程
*/
public function run(){
echo
"正在运行 \n";
for(
$i=0; $i<10000; $i++){
$jobID = rand(0,10000000000000);
$launched = $this->launchJob($jobID);
}

// 等待子进程完成,然后在此退出
while(count($this->currentJobs)){
echo
"等待当前作业完成... \n";
sleep(1);
}
}

/**
* 从作业队列中启动作业
*/
protected function launchJob($jobID){
$pid = pcntl_fork();
if(
$pid == -1){
// 启动作业时出现问题
error_log('无法启动新作业,退出');
return
false;
}
else if (
$pid){
// 父进程
// 有时候,如果子脚本执行得足够快,您可能会在执行此代码之前收到 childSignalHandler 函数的信号!
//
$this->currentJobs[$pid] = $jobID;

// 如果在到达这里之前捕获了针对此 pid 的信号,它将位于我们的 signalQueue 数组中
// 因此,让我们现在处理它,就好像我们刚刚收到了信号一样
if(isset($this->signalQueue[$pid])){
echo
"在信号队列中找到 $pid,正在处理它 \n";
$this->childSignalHandler(SIGCHLD, $pid, $this->signalQueue[$pid]);
unset(
$this->signalQueue[$pid]);
}
}
else{
// 分叉的子进程,执行您的操作....
$exitStatus = 0; // 如果需要或任何错误代码
echo "在 pid 中做一些有趣的事情 ".getmypid()."\n";
exit(
$exitStatus);
}
return
true;
}

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

// 如果没有提供 pid,这意味着我们从系统中获取信号。 让我们弄清楚
// 哪个子进程结束
if(!$pid){
$pid = pcntl_waitpid(-1, $status, WNOHANG);
}

// 确保我们获取所有已退出的子进程
while($pid > 0){
if(
$pid && isset($this->currentJobs[$pid])){
$exitCode = pcntl_wexitstatus($status);
if(
$exitCode != 0){
echo
"$pid 以状态 ".$exitCode."\n";
}
unset(
$this->currentJobs[$pid]);
}
else if(
$pid){
// 哦不,我们的作业在父进程甚至注意到它已被启动之前就完成了!
// 让我们记录下来,并在父进程准备好的时候处理它
echo "..... 将 $pid 添加到信号队列 ..... \n";
$this->signalQueue[$pid] = $status;
}
$pid = pcntl_waitpid(-1, $status, WNOHANG);
}
return
true;
}
}
2
gaylord at 100days dot de
14 年前
如果您激活了 PHP 信号处理程序 (pcntl_signal),pcntl_wait 不会在信号上终止。
除非信号处理程序是用第三个参数 = true 激活的。

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

这不会在发送到进程的 SIGTERM 上终止,因为在 php 接收到信号后 "wait" 会重新启动。除非 pcntl_wait 因其他原因终止,否则信号处理程序 "myHandler" 不会被调用。

更改为
<?php
declare(ticks=1);
pcntl_signal(SIGTERM, "myHandler", true);
$pid=pcntl_wait($status);
?>

现在,当信号到来时,pcntl_wait 会终止,并且 "myHandler" 会在 SIGTERM 上被调用。(不过,请确保将 wait 放入循环中,因为它现在不仅会在子进程退出时终止,还会在信号到达时终止。测试 $pid>0 以检测来自子进程的退出消息)
(感谢 Andrey 帮助我调试此问题)
0
duerra at yahoo dot com
14 年前
糟糕,我在之前的评论中从作业守护进程代码中删除了一点内容。您需要在调用 ->launchJob() 方法之前添加一行代码

<?php

while(count($this->currentJobs) >= $this->maxProcesses){
echo
"允许的最大子进程数,等待...\n";
sleep(1);
}
0
digitalaudiorock at gmail dot com
14 年前
我无法在有活动信号处理程序的情况下使 pcntl_wait 或 pcntl_waitpid 终止。然后,我注意到下面 gaylord at 100days dot de 的帖子,但是我对该帖子有点困惑,因为我发现情况正好相反。pcntl_signal 的第三个参数 (restart_syscalls 参数) 的默认值为 true,这似乎会导致 wait 在信号到达时继续。为了防止这种情况,我必须明确将其设置为 false。也就是说

pcntl_signal(SIGTERM, 'my_handler_function', false);
0
thisisroot at gmail dot com
18 年前
下面是分叉一些子进程并计时总持续时间的简单示例(对压力测试很有用)。

<?php

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

/* 分叉你!
* (抱歉,我必须这样做)
*/
$ceiling = $CONCURRENCY - 1;

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

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

}

}

/* 处理主体 */
echo "在这里做些事情\n";

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

echo
"在 " . ( microtime( true) - $start) . " 秒内完成。\n";

}

?>
-1
thomas dot nicolai at unisg dot ch
18 年前
之前的代码对我来说不起作用,因为子进程正确启动,但在它们死亡后没有刷新。因此请记住使用此代码,并使用信号处理程序来了解子进程何时退出,以便知道何时启动新的子进程。我在 {andy at cbeyond dot net} 的帖子中添加了几行,因为他的帖子对我也不起作用(PHP5.1)。效果与下面的代码相同。

<?php
declare(ticks = 1);

$max=5;
$child=0;

// 信号处理程序函数
function sig_handler($signo) {
global
$child;
switch (
$signo) {
case
SIGCHLD:
echo
"收到 SIGCHLD 信号\n";
$child--;
}
}

// 安装信号处理程序以处理死掉的子进程
pcntl_signal(SIGCHLD, "sig_handler");

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

if (
$pid == -1) {
die(
"无法分叉");
} else if (
$pid) {

// 我们是父进程
if ( $child >= $max ){
pcntl_wait($status);
$child++;
}
} else {
// 我们是子进程
echo "\t 启动新的子进程 | 现在我们有了 $child 个子进程\n";
// 假设正在执行一些有趣的事情
sleep(rand(3,5));
exit;
}
}
?>
To Top