2024 年 PHP Conference Japan

PCNTL 函式

另請參閱

參考關於 POSIX 函式 的章節可能會有幫助。

目錄

新增註解

使用者貢獻的註解 18 則註解

kevin at vanzonneveld dot net
16 年前
如果您想在 PHP 中建立守護行程,請考慮使用 System_Daemon http://pear.php.net/package/System_Daemon

像這樣安裝它
pear install -f system_daemon

然後像這樣使用它
<?php
// 引用 PEAR 的 Daemon 類別
require_once "System/Daemon.php";

// 最基本的設定
System_Daemon::setOption("appName", "mydaemonname");

// 產生 Daemon!
System_Daemon::start();

// 您的 PHP 程式碼放在這裡!
while (true) {
doTask();
}

// 停止 daemon!
System_Daemon::stop();
?>

更多範例可以在 PEAR 套件中找到。
registrazioni at XSPAMX dot tassetti dot net
17 年前
(PHP 5.2.4)

這是一個多執行緒的例子,保持與 MySQL 資料庫的不同連線:當子程序退出時,它們會關閉連線,而其他子程序將無法再使用它,從而產生問題。在這個例子中,我使用了可變變數來為每個子程序建立不同的連線。

這個腳本會永遠循環執行,一個母程序與終端分離,五個子程序的「名稱」從 1 到 5。當一個子程序在資料庫中看到它的名稱時(一個名為 'test.tb' 的表格,只有一個欄位 'test'),它就會讓自己結束。要殺死子程序,請在資料庫中插入它們的值。母程序只會在所有子程序都死亡時自殺。

真是個悲傷但有趣的故事...

$npid = pcntl_fork(); // 與終端分離並由 INIT 回收

如果 ($npid==-1) 就 die("錯誤:無法 pcntl_fork()\n");
否則,如果 ($npid) 就 exit(0); // 祖父程序結束
否則 // 母程序繼續產生子程序
{
$children = 5;
for ($i=1; $i<=$children; $i++)
{
$pid = pcntl_fork();
如果 ($pid==-1) 就 die("錯誤:無法 pcntl_fork()\n");
否則,如果 ($pid)
{
$pid_arr[$i] = $pid;
}
如果 (!$pid) // 子程序
{
global $vconn;
$vconn = "vconn$i";
global $$vconn;
$$vconn = @mysql_connect("mydbhost","mydbuser","mydbpwd");
如果 (!($$vconn)) 就 echo mysql_error();
如果 (!($$vconn)) 就 exit;

while (1)
{
$query = "SELECT test FROM test.tb";
$rs = mysql_query($query,$$vconn);
$rw = mysql_fetch_row($rs);
如果 ($rw[0]==$i) 就 exit;
否則
{
echo "資料庫是 $rw[0] 而我是 $i,還沒輪到我,我會等待....\n";
sleep(1);
}
}
}
}

foreach ($pid_arr as $pid)
{
// 我們是母程序,我們等待所有子程序結束
pcntl_waitpid($pid, $status);
}
echo "我所有的子程序都結束了,我要自我了斷...\n";
exit();
}
kementeusNOSPAM at gmail dot com
17 年前
在文件範例中,我們需要將 pctnl_signal 陳述式放在 while 迴圈**之前**。

這樣我們就可以執行在訊號處理函式中放置的任何內容。
David Koopman
21 年前
我很難找到一個完整的範例,說明如何使用 PHP 作為使用連線池的多程序(或多執行緒 - 我不了解這兩個術語的區別)守護程序。我把拼圖的碎片拼湊在一起,得到了下面的程式。我希望它能幫助到其他人。關於如何讓它運作的注意事項

1) 我使用以下設定選項在我的機器上重建了 PHP
./configure --enable-sockets --enable-pcntl --enable-sigchild
make
make install

2) 當我嘗試自行處理 SIGTERM 和 SIGHUP 時遇到了問題,所以我從我的程式碼中刪除了這些,除非您有特殊需求,否則不要使用它們
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGHUP, "sig_handler");

我做的是
1. 啟動程式,然後 fork 以從終端分離(結束父程序並使子程序成為會話領導者)。

2. 綁定到地址和連接埠並開始監聽。

3. fork $poolNum 次,建立 $poolNum 個子程序(這是正在執行的守護程序池。子程序處理傳入的連線)。

4. 保持父程序在迴圈中執行,不斷檢查是否應該建立新的子程序。它將始終保持 $poolNum 個備用子程序準備就緒(只要總共的連線池不超過 $maxDaemon)。隨著連線的進入,會產生更多子程序。

5. 當新的連線進入時,它會被移交給第一個子程序。然後,這個子程序會向父程序發送一個 SIGUSR1 訊號。父程序有一個 SIGUSR1 的訊號處理程序,它會將 $numActive 變數加一。正在執行的迴圈(參見上面的 4)將注意到 $numActive 的增加,並自動建立一個新的子程序以保持程序池的運作。

我必須在下一則筆記中發布程式碼,這個網站的筆記引擎不允許發布這麼長的筆記,但我認為這個程式碼範例很值得評論...
luca dot mariano at email dot it
21 年前
大家好,
如果有人在 Win32 上使用 PHP-CLI 並且想要體驗 PCNTL 的東西,我已經打包了一個內建 pcntl、shmop、sysvshm 和其他典型 Unix 擴充功能的 PHP 二進制版本...(感謝 Cygwin DLL)。
下載連結:http://phplet.sf.net/modules.php?name=Web_Links&l_op=visit&lid=4
debasiss at mindfiresolutions dot com
5 年前
<?php
/**
* @file
* Working demo of simulating parallel process in PHP.
*/

// **** **** //
print "Main program started.... \n";
// Start two child processes.
createProcess("Job1");
createProcess("Job2");
print
" *** Child Process started *** \n";

while (
TRUE) {

$pid = pcntl_waitpid(0, $status, WNOHANG);
if (
$pid > 0) {
childProcessComplete($pid);
}else if(
$pid === -1){
print
" *** Child Process Completed *** \n";
allChildProcessComplete();
exit();
}

}
print
"Main program end. \n";

/**
* Method to start child process.
*/
function startChildProcess($childProcessName) {
$executionTime = rand(5, 10);

if(
$childProcessName === "Job1"){
print
"Starting Job1 on processId " .getmypid()." at " . date('l jS \of F Y h:i:s A') . " expected time $executionTime seconds\n";
}else if(
$childProcessName === "Job2"){
print
"Starting Job2 on processId " .getmypid()." at " . date('l jS \of F Y h:i:s A') . " expected time $executionTime seconds\n";
}

// Simulate doing actual work with sleep().
sleep($executionTime);
}

/**
* Method to notify when system unable to start a process.
*/
function errorOnProcessLunch() {
print
"Failed to lunch process \n";
}

/**
* Method to notify when a child process complete the task.
*/
function childProcessComplete($pid) {
print
"Child processing is done for $pid at " . date('l jS \of F Y h:i:s A') . " \n";
}

/**
* Method to create a new process.
*/
function createProcess($pname) {
$pid = pcntl_fork();

if (
$pid == -1) {
errorOnProcessLunch();
}
else if (
$pid === 0) {
startChildProcess($pname);
exit();
// Make sure to exit.
}
else {
startParentProcess($pid);
}

}

/**
* Method to notify when parent thread execute.
*/
function startParentProcess($childProcessID){
print
"In parent thread created child processid $childProcessID \n";
}

/**
* Method to notify when all child process has completed the task.
*/
function allChildProcessComplete(){
print
"All child processing completed \n";
}

?>
jeremy at nirvani dot net
21 年前
#!/usr/local/bin/php -q
<?php

# Jeremy Brand <jeremy@nirvani.net>
# http://www.nirvani.net/

# ./configure --enable-pcntl --enable-sigchild
# make
# make install

# 這段程式碼示範如何使用腳本進行多進程處理。每次
# 執行此腳本時,結果會產生 5 個(在此範例中)進程來
# 完成指定的工作。

# 例如訊息佇列。您可以取得佇列中的訊息數量,並非同步處理任何或所有訊息。

# 透過執行此腳本一次來取得您想要產生的子進程數量。
$children = 5; # 這裡可能是一個函式呼叫。

for ($i=1; $i<=$children; $i++)
{

$pid = pcntl_fork();
if (
$pid == -1)
{
die(
"無法建立子進程\n");
}
else if (
$pid)
{
# 如果我們是父進程,我們已經完成了產生子進程的工作,
# 現在讓我們完成工作並結束!
exit(0);
}
else
{
# 由於我們是子進程,因此再次 fork,以便 init 成為我們的父進程。Init
# 擅長 wait() 子進程 - 例如:回收。
$cpid = pcntl_fork();
if (
$cpid == -1)
{
die(
"在子進程中無法建立子進程\n");
}
if (!
$cpid)
{
# 現在我們已經從父進程 fork 出來,並且也沒有等待任何
# 其他子進程處理,然而其他子進程與我們同時運行。
# 請確保您在此處編寫的程式碼在此多進程環境中安全運行 - 例如:適當的鎖定等。
# 在此處編寫您想要進行多進程處理的自訂程式碼。

# 在此處新增要進行多進程處理的程式碼
print "我們是子進程編號 $i\n";

# 子進程處理完成後,別忘了結束。當然,
# 如果需要,請更改此結束程式碼。
exit(0);
}
}
}

?>
keksov[at]gmx.de
22 年前
您必須在 socket_accept 之前使用 socket_select,這樣您的程式碼就會使用 select 等待連線。 socket_select 很容易被訊號中斷。以下是我程式庫中的一個範例(TNetSocket 類別的方法)
//-- select
function select($aread=NULL,$awrite=NULL,$aexcept=NULL,$timeout=NULL)
{
while(1)
{
$res="";
$res=socket_select($aread, $awrite, $aexcept, $timeout);

// 如果 errno===0 表示 select 被 SysV 信號中斷
if($res===false && socket_last_error($this->socket())!==0)
{ // 發生錯誤,並非被信號中斷
$this->set_socket_error(__LINE__);
return(false);
}
break;
}
return(true);
}

//-- 接受連線,等待連線進來
function accept()
{
$this->clear_socket_error();
$this->set_io_socket(_SOCKET_);

$socket=$this->socket();
$aread=array($socket);
if ($this->select($a=&$aread)===false)
return(false);

$child_socket=socket_accept($this->socket());
if($child_socket <= 0)
{ // 發生錯誤
$this->set_socket_error(__LINE__);
return(false);
}

$this->child_socket=$child_socket;
$this->sockets[_CHILD_SOCKET_]=&$this->child_socket;
$this->set_io_socket(_CHILD_SOCKET_);

$a=&$this->peername;
$res=socket_getpeername($child_socket,$a);

if($res <= 0)
{ // 發生錯誤
$this->set_socket_error(__LINE__);
return(false);
}

$this->get_address_and_port(_CHILD_SOCKET_);
TLogManager::phpserv("已接受連線。地址 $this->address,連接埠 $this->port","net_socket",__FILE__,__LINE__);

$this->connected=true;
return(true); // 返回新的 TNetSocket 類型物件
}
匿名
21 年前
在範例 1 中,我發現除非我在 pcnt_signal 之前建立 sig_handler 函式,否則它無法作用.. 而且會直接失效。

(給遇到這類問題的人的註記)
cameronNO_SPAM at tripdubdev dot com
21 年前
我目前正在編寫一些程式碼來處理這個問題,但如果我忘記回來發文,或者如果我需要一些時間,為什麼不讓一個獨立的背景作業程式持續執行(透過 shell 啟動),以追蹤哪些 socket 可供客戶端使用呢?這樣你只需要與在背景執行的單一作業(或它自己的小型伺服器)通訊,該作業會維護一個伺服器可用 socket 的陣列。這似乎是最自然的替代方案,因為 PHP 聲明不應該在網路伺服器環境中使用流程控制功能。我不希望建置一個伺服器,尤其是高流量的伺服器,必須透過迴圈來尋找可用的 socket。
schst at php dot net
21 年前
要擺脫子行程終止時的殭屍行程,您不需要編寫大量使用複雜機制(如訊息佇列)的程式碼。
您只需設定一個信號處理程式

pcntl_signal(SIGCHLD, SIG_IGN);

Stephan
daniel[at]lorch.cc
22 年前
這段程式碼幫助我找出傳送到我的行程的信號

function sig_identify($signo) {
switch($signo) {
case SIGFPE: return 'SIGFPE';
case SIGSTOP: return 'SIGSTOP';
case SIGHUP: return 'SIGHUP';
case SIGINT: return 'SIGINT';
case SIGQUIT: return 'SIGQUIT';
case SIGILL: return 'SIGILL';
case SIGTRAP: return 'SIGTRAP';
case SIGABRT: return 'SIGABRT';
case SIGIOT: return 'SIGIOT';
case SIGBUS: return 'SIGBUS';
case SIGPOLL: return 'SIGPOLL';
case SIGSYS: return 'SIGSYS';
case SIGCONT: return 'SIGCONT';
case SIGUSR1: return 'SIGUSR1';
case SIGUSR2: return 'SIGUSR2';
case SIGSEGV: return 'SIGSEGV';
case SIGPIPE: return 'SIGPIPE';
case SIGALRM: return 'SIGALRM';
case SIGTERM: return 'SIGTERM';
case SIGSTKFLT: return 'SIGSTKFLT';
case SIGCHLD: return 'SIGCHLD';
case SIGCLD: return 'SIGCLD';
case SIGIO: return 'SIGIO';
case SIGKILL: return 'SIGKILL';
case SIGTSTP: return 'SIGTSTP';
case SIGTTIN: return 'SIGTTIN';
case SIGTTOU: return 'SIGTTOU';
case SIGURG: return 'SIGURG';
case SIGXCPU: return 'SIGXCPU';
case SIGXFSZ: return 'SIGXFSZ';
case SIGVTALRM: return 'SIGVTALRM';
case SIGPROF: return 'SIGPROF';
case SIGWINCH: return 'SIGWINCH';
case SIGPWR: return 'SIGPWR';
}
}

function sig_handler($signo) {
echo "攔截到 " . sig_identify($signo) . " (" . $signo . ") 於行程 " . posix_getpid() . "\n";
}

pcntl_signal(SIGFPE, "sig_handler");
pcntl_signal(SIGHUP, "sig_handler");
// pcntl_signal(SIGINT, "sig_handler");
pcntl_signal(SIGQUIT, "sig_handler");
pcntl_signal(SIGILL, "sig_handler");
pcntl_signal(SIGTRAP, "sig_handler");
pcntl_signal(SIGABRT, "sig_handler");
pcntl_signal(SIGIOT, "sig_handler");
pcntl_signal(SIGBUS, "sig_handler");
pcntl_signal(SIGPOLL, "sig_handler");
pcntl_signal(SIGSYS, "sig_handler");
pcntl_signal(SIGCONT, "sig_handler");
pcntl_signal(SIGUSR1, "sig_handler");
pcntl_signal(SIGUSR2, "sig_handler");
pcntl_signal(SIGSEGV, "sig_handler");
pcntl_signal(SIGPIPE, "sig_handler");
pcntl_signal(SIGALRM, "sig_handler");
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGSTKFLT, "sig_handler");
pcntl_signal(SIGCHLD, "sig_handler");
pcntl_signal(SIGCLD, "sig_handler");
pcntl_signal(SIGIO, "sig_handler");
pcntl_signal(SIGTSTP, "sig_handler");
pcntl_signal(SIGTTIN, "sig_handler");
pcntl_signal(SIGTTOU, "sig_handler");
pcntl_signal(SIGURG, "sig_handler");
pcntl_signal(SIGXCPU, "sig_handler");
pcntl_signal(SIGXFSZ, "sig_handler");
pcntl_signal(SIGVTALRM, "sig_handler");
pcntl_signal(SIGPROF, "sig_handler");
pcntl_signal(SIGWINCH, "sig_handler");
pcntl_signal(SIGPWR, "sig_handler");

我註解掉了 SIGINT,因為它是當您按下 CTRL-C 時傳送到您行程的訊號。如果您攔截此訊號,您必須妥善處理它

function sig_handler($signo) {
switch($signo) {
case SIGINT
// 自訂清除程式碼
exit; // 現在退出
break;
}
}

否則,停止行程的唯一方法是發送 SIGKILL 訊號 - 您可以在 shell 中輸入「kill -9 PID」來執行此操作(其中 -9 是 SIGKILL 的數值)。

注意:基於顯而易見的原因,您無法為 SIGSTOP 和 SIGKILL 新增處理常式(即忽略訊號)。
m dot quinton at gmail dot com
16 年前
一個使用 pcntl_fork 管理任務的類別框架的好例子。

<?php

/**
* author : Marc Quinton / April 2008.
*
* a simple task management framework using pcntl_fork, pcntl_wait.
*
* - see at bottom for a sample usage.
* - you shoud overring Task class (SleepingClass is an example), and manage them in a pool, using taskManager
*/

error_reporting(E_ALL);

class
Task {

protected
$pid;
protected
$ppid;

function
__construct(){
}

function
fork(){
$pid = pcntl_fork();
if (
$pid == -1)
throw new
Exception ('fork error on Task object');
elseif (
$pid) {
# we are in parent class
$this->pid = $pid;
# echo "< in parent with pid {$his->pid}\n";
} else{
# we are is child
$this->run();
}
}

function
run(){
# echo "> in child {$this->pid}\n";
# sleep(rand(1,3));
$this->ppid = posix_getppid();
$this->pid = posix_getpid();
}

# call when a task in finished (in parent)
function finish(){
echo
"task finished {$this->pid}\n";
}

function
pid(){
return
$this->pid;
}
}

class
SleepingTask extends Task{
function
run(){
parent::run();
echo
"> in child {$this->pid}\n";

# print_r($this);

sleep(rand(1,5));
echo
"> child done {$this->pid}\n";
exit(
0);
}
}

class
TaskManager{

protected
$pool;

function
__construct(){
$this->pool = array();
}

function
add_task($task){
$this->pool[] = $task;
}

function
run(){

foreach(
$this->pool as $task){
$task->fork();
}

# print_r($this);
# sleep(60);

while(1){
echo
"waiting\n";
$pid = pcntl_wait($extra);
if(
$pid == -1)
break;

echo
": task done : $pid\n";
$this->finish_task($pid);
}

echo
"processes done ; exiting\n";
exit(
0);
}

function
finish_task($pid){
if(
$task = $this->pid_to_task($pid))
$task->finish();
}

function
pid_to_task($pid){
foreach(
$this->pool as $task){
if(
$task->pid() == $pid)
return
$task;
}
return
false;
}
}

$manager = new TaskManager();

for(
$i=0 ; $i<10 ; $i++)
$manager->add_task(new SleepingTask());

$manager->run();

?>
flyingguillotine1968 at yahoo dot com
17 年前
好的,以下是處理子行程的方法:>

您需要使用 pfork 建立主程式的分支,並讓它退出,
允許新的子行程成為主程式(父行程)。

範例虛擬碼
主程式開始;
....
如果 pfork 子行程成功
則退出;

// 現在您的新子行程已掌控...
// 使用訊號處理常式使其退出(可能是 sigkill 或 sigterm)
// 當這個新行程 pfork 時,將新的子行程 PID 儲存在一個
// 陣列中。當您的行程攔截到 sigterm 訊號時
// 迴圈遍歷其子行程 PID 的陣列,向每個子行程發送
// sigkill,然後對它們呼叫 pwait 以等待它們退出。
// 這將確保所有子行程都被清除乾淨
// 然後.. 現在就是訣竅....
// 將您的 sigterm 處理常式重置回其原始預設處理常式,
// 然後再次觸發 sigterm,主程式現在也可以正常退出了:>


範例
當您的 pfork 主行程收到 sigterm 時... 執行以下操作
類似這樣

foreach ($this->pForkList as $kiddie) {
$deadPID = 0;
$this->sendSignal(SIGKILL,$kiddie);
do {
$deadPID = pcntl_waitpid(-1,WNOHANG);
if ($deadPID > 0) {
// 子行程現在已退出...
unset($this->pForkList[ array_search($deadPID,$this->pForkList)]);
break;
} // if 結束
} while ($deadPID == 0);

} // for 結束

// 現在重置 sigterm ...
$this->signalSet($sigNum,SIG_DFL);
// 現在觸發 sigterm ...
$this->sendSignal(SIGTERM,$this->myPID);

這將允許您的主行程及其所有子行程正確退出,而不會留下任何殭屍行程或其他不良的東西!
van[at]webfreshener[dot]com
22 年前
在 PHP 中,Forking 你的 Daemon 程序會導致它在退出時變成殭屍程序。

...至少我在以下系統上觀察到這個現象:
FreeBSD (PHP4.2.x)
Debian (PHP4.3.0-dev)
Darwin (PHP4.3.0-dev)

這個問題已使用上述範例程式碼和其他評估用的腳本進行測試。

在 configure 時加入 `--enable-sigchild` 參數似乎可以解決這個問題。

希望這能幫你省下一些抓狂的時間 :]
andy at cbeyond dot net
20 年前
假設你想 fork 出子程序來處理數百個不同的目標(例如,SNMP 輪詢,但這只是一個例子)。由於你不想讓自己 fork-bomb,以下是一種限制同時運作的子程序數量的方法:

#!/usr/bin/php -q
<?php
declare(ticks = 1);

$max=10;
$child=0;

// 訊號處理函式
function sig_handler($signo) {
global
$child;
switch (
$signo) {
case
SIGCHLD:
echo
"收到 SIGCHLD 訊號\n";
$child -= 1;
}
}

// 安裝子程序結束的訊號處理函式
pcntl_signal(SIGCHLD, "sig_handler");

for (
$i=1;$i<=20;$i++) {
while (
$child >= $max) {
sleep(5); echo "\t 已達子程序數量上限\n";
}
$child++;
$pid=pcntl_fork();
if (
$pid == -1) {
die(
"無法 fork 子程序");
} else if (
$pid) {
// 父程序
} else {
// 子程序
echo "子程序編號 $i\n";
// 在此執行特定任務
sleep(15);
exit;
}
}
corychristison at lavacube dot com
20 年前
相較於第一則留言的建議,要取得應用程式收到的訊號,更簡單的方法是使用 `get_defined_constants()` 列出所有常數,迴圈過濾掉非訊號的常數,並檢查其值是否相符。

以下是用 PHP5 編寫的程式碼範例:
<?php

// php5 特定

function pcntl_sig_identify ( $sig_no ) {
$get_constants = get_defined_constants(true);
$pcntl_contstants = $get_constants["pcntl"];
$keys = array_keys( $pcntl_contstants );
foreach(
$keys as $key){
if(
strstr($key, "SIG") && !strstr($key, "_") && $pcntl_contstants[$key] == $sig_no){
return
$key;
}
}
// 結束迴圈
} // 結束函式 pcntl_sig_identify

//
// 這個範例會輸出 "SIGTERM"
//

print pcntl_sig_identify(15) . "\n";

?>
Patrice Levesque
21 年前
所以,您想要建立多個子行程,並且不想要任何殭屍行程,對嗎?

您可以使用 IPC 來達成此目的。每個產生的子行程都必須告知其父行程,它該被終止了。所以,是的,殭屍行程會被建立,但父行程會不時地「清理」其子行程。程式碼

<?php
declare(ticks = 1);
// create a IPC message queue
$msgqueue = msg_get_queue(ftok("/tmp/php_msgqueue.stat", 'R'),0666 | IPC_CREAT);
// loop for 1000 children
for ($c = 0; $c < 1000; $c++) {
// fork
$pcid = pcntl_fork();
if (
$pcid == -1) {
die(
"Could not fork!");
}
elseif (
$pcid) { // we are the parent, look for zombie kids and terminate 'em
// look in the IPC message queue if there are any entries
$currentqueue = msg_stat_queue($msgqueue);
$n = $currentqueue['msg_qnum']; // number of messages (number of kids to terminate)
if ($n > 0) {
echo
"There are $n kids to terminate.\n";
for (
$i = 0; $i < $n; $i++) {
// pop the kid's PID from the IPC message queue
if (!msg_receive ($msgqueue, 1, $msg_type, 16384, $msg, true, 0, $msg_error)) {
echo
"MSG_RECV ERROR: $errmsg \n"; // something has gone wrong
}
else {
pcntl_waitpid($msg, $tmpstat, 0); // terminate kid for real.
};
};
};
}
else {
// we are the child!
if (!posix_setsid()) { die ("Could not detach"); }; // detach
echo "I am child number $c\n";
sleep(5); // do something useful
// tell dad I'm finished via IPC: send him my PID
if (!msg_send($msgqueue, 1, posix_getpid(), true, true, $errmsg)) {
echo
"MSG_SEND ERROR: $errmsg \n";
};
exit();
// become a zombie until dad kills me
};
};
?>
To Top