2024年PHP开发者大会日本站

pcntl_wait

(PHP 5, PHP 7, PHP 8)

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

描述

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

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

注意:

此函数等效于使用 -1 process_id 和没有 flagspcntl_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 的值是以下两个常量 OR 运算的结果。

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

返回值

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

参见

添加注释

用户贡献的注释 7 条注释

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(
"无法分叉");
} elseif (
$pid) {
echo
"我是父进程 $i\n";
$execute++;
if (
$execute>=MAXPROCESS){
pcntl_wait($status);
$execute--;
}
} else {
echo
"我是子进程, $i pid = $pid \n";
sleep(rand(1,3));
echo
"再见 $i\n";
exit;
}
}
?>
duerra at yahoo dot com
14 年前
在某些情况下,使用 pcntl_fork() 可能有点棘手。对于快速作业,子进程可能在父进程执行与启动进程相关的某些代码之前完成处理。父进程可能会在准备好处理子进程状态之前收到信号。为了处理这种情况,我在信号处理程序中向进程“队列”添加一个 ID,如果父进程尚未准备好处理它们,则需要清理这些 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;
}
}
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 会终止,并且将在 SIGTERM 上调用“myHandler”。(但是请确保将 wait 放入循环中,因为它现在不仅会在子进程退出时终止,还会在信号到达时终止。测试 $pid>0 以检测来自子进程的退出消息)
(感谢 Andrey 帮助我调试这个问题)
duerra at yahoo dot com
14 年前
糟糕,我从之前的评论中的作业守护程序代码中删除了一点点内容。您需要在调用 ->launchJob() 方法之前添加一行

<?php

while(count($this->currentJobs) >= $this->maxProcesses){
echo
"已达到最大子进程数,等待...\n";
sleep(1);
}
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);
thisisroot at gmail dot com
19 年前
下面是一个分叉一些子进程并计时总持续时间(对于压力测试很有用)的简单示例。

<?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";

}

?>
[email protected]
18 年前
之前的代码对我来说不起作用,因为子进程虽然正确启动,但在它们结束后没有刷新。因此请记住使用此代码,并使用信号处理程序来了解子进程何时退出,以便知道何时启动新进程。我根据 {[email protected]} 的帖子添加了几行,因为他的帖子对我也不起作用(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