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 且沒有 flags 參數來呼叫 pcntl_waitpid()

參數

status

pcntl_wait() 會將狀態資訊儲存在 status 參數中,可以使用以下函式進行評估:pcntl_wifexited()pcntl_wifstopped()pcntl_wifsignaled()pcntl_wexitstatus()pcntl_wtermsig()pcntl_wstopsig()

flags

如果您的系統(大多數 BSD 類型系統)上可以使用 wait3,您可以提供選用的 flags 參數。如果未提供此參數,系統呼叫將使用 wait。如果 wait3 不可用,提供 flags 值將沒有任何作用。flags 的值是以下兩個常數中的一個或多個值透過 OR 運算組合而成:

flags 的可能值
WNOHANG 如果沒有子程序退出,則立即返回。
WUNTRACED 返回已停止且其狀態尚未回報的子程序。

傳回值

pcntl_wait() 會傳回已退出的子程序的程序 ID;如果發生錯誤,則傳回 -1;如果提供了 WNOHANG 作為選項(在支援 wait3 的系統上)且沒有可用的子程序,則傳回 0。

另請參閱

新增筆記

使用者貢獻的筆記 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 新增到需要清理的程序「佇列」中,如果父程序尚未準備好處理它們的話。

<?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;
}
}
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 訊號被送到這個程序,它並不會終止,因為 `wait` 會在 PHP 收到訊號後重新啟動。訊號處理器 `myHandler` 不會被呼叫,除非 `pcntl_wait` 因其他原因終止。

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

現在,當訊號進來時,`pcntl_wait` 會終止,並且 `myHandler` 會在收到 SIGTERM 時被呼叫。(請務必將 `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 年前
以下是一個簡單的例子,用於 fork 一些子程序並計時總持續時間(對壓力測試很有用)。

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

}

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