PCNTL 函数

参见

查看关于 POSIX 函数 的部分可能会有帮助。

目录

添加笔记

用户贡献笔记 18 笔记

10
kevin at vanzonneveld dot net
15 年前
如果您想在 PHP 中创建守护进程,请考虑使用 System_Daemon http://pear.php.net/package/System_Daemon

像这样安装它
pear install -f system_daemon

然后像这样使用它
<?php
// 包含 PEAR 的守护进程类
require_once "System/Daemon.php";

// 最小限度的设置
System_Daemon::setOption("appName", "mydaemonname");

// 生成守护进程!
System_Daemon::start();

// 您的 PHP 代码在此!
while (true) {
doTask();
}

// 停止守护进程!
System_Daemon::stop();
?>

可以在 PEAR 包中找到更多示例。
10
registrazioni at XSPAMX dot tassetti dot net
16 年前
(PHP 5.2.4)

这是一个多线程示例,它保持与 mysql 数据库的不同连接:当子进程退出时,它们会关闭连接,其他子进程无法再使用它,从而导致问题。在这个示例中,我使用了变量变量为每个子进程创建不同的连接。

此脚本永远循环,一个母进程与终端分离,由 INIT 收割,五个子进程从 1 到 5 命名。当一个子进程在数据库中看到它的名字时(一张名为 'test.tb' 的表,只有一个名为 'test' 的字段),它就让它自己死亡。要杀死子进程,请将它们的值插入数据库。母进程只有在所有子进程都死亡后才会自杀。

一个悲伤但有趣的故事情节...

$npid = pcntl_fork(); // 与终端分离,由 INIT 收割

if ($npid==-1) die("错误:无法 pcntl_fork()\n");
else if ($npid) exit(0); // 爷爷死了
else // 母亲继续生孩子
{
$children = 5;
for ($i=1; $i<=$children; $i++)
{
$pid = pcntl_fork();
if ($pid==-1) die("错误:无法 pcntl_fork()\n");
else if ($pid)
{
$pid_arr[$i] = $pid;
}
if (!$pid) // 子进程
{
global $vconn;
$vconn = "vconn$i";
global $$vconn;
$$vconn = @mysql_connect("mydbhost","mydbuser","mydbpwd");
if (!($$vconn)) echo mysql_error();
if (!($$vconn)) exit;

while (1)
{
$query = "SELECT test FROM test.tb";
$rs = mysql_query($query,$$vconn);
$rw = mysql_fetch_row($rs);
if ($rw[0]==$i) exit;
else
{
echo "数据库是 $rw[0],我是 $i,现在还不到我的时间,我将等待....\n";
sleep(1);
}
}
}
}

foreach ($pid_arr as $pid)
{
// 我们是父进程,我们等待所有子进程死亡
pcntl_waitpid($pid, $status);
}
echo "我的所有孩子都死了,我要自杀...\n";
exit();
}
2
kementeusNOSPAM at gmail dot com
17 年前
在文档示例中,我们需要在 while 循环之前放置 pctnl_signal 语句。

这样,我们就可以执行在信号处理程序函数中放置的任何内容。
1
David Koopman
20 年前
我很难找到使用 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. 启动程序,然后分叉以脱离终端(杀死父进程并将子进程设为会话领导者)。

2. 绑定到地址和端口并开始监听。

3. 分叉 $poolNum 次,创建 $poolNum 个子进程(这是运行的守护进程池。子进程处理传入的连接)。

4. 使父进程在循环中运行,不断检查它是否应该创建新的子进程。它始终保持 $poolNum 个备用子进程(只要总池连接不超过 $maxDaemon)。随着连接的传入,将生成更多子进程。

5. 当新的连接传入时,它将传递给第一个子进程。然后,该子进程向父进程发送 SIGUSR1 信号。父进程有一个 SIGUSR1 的信号处理程序,该处理程序将 $numActive 变量递增一。正在运行的循环(见上面的第 4 点)将注意到 $numActive 的递增,并自动创建一个新的子进程以保持进程池的运行。

我必须在下一条笔记中发布代码,此网站上的笔记引擎不允许发布如此长的笔记,但我认为这个代码示例值得在这上面发表评论...
1
luca dot mariano at email dot it
20 年前
嗨,各位,
如果有人在 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
2
debasiss at mindfiresolutions dot com
5 年前
<?php
/**
* @file
* 模拟PHP并行进程的工作演示。
*/

// **** **** //
print "主程序已启动.... \n";
// 启动两个子进程。
createProcess("Job1");
createProcess("Job2");
print
" *** 子进程已启动 *** \n";

while (
TRUE) {

$pid = pcntl_waitpid(0, $status, WNOHANG);
if (
$pid > 0) {
childProcessComplete($pid);
}else if(
$pid === -1){
print
" *** 子进程已完成 *** \n";
allChildProcessComplete();
exit();
}

}
print
"主程序结束。 \n";

/**
* 启动子进程的方法。
*/
function startChildProcess($childProcessName) {
$executionTime = rand(5, 10);

if(
$childProcessName === "Job1"){
print
"启动Job1,进程ID为" .getmypid().",启动时间为" . date('l jS \of F Y h:i:s A') . ",预计执行时间为" $executionTime 秒\n";
}else if(
$childProcessName === "Job2"){
print
"启动Job2,进程ID为" .getmypid().",启动时间为" . date('l jS \of F Y h:i:s A') . ",预计执行时间为" $executionTime 秒\n";
}

// 使用sleep()模拟实际工作。
sleep($executionTime);
}

/**
* 系统无法启动进程时的通知方法。
*/
function errorOnProcessLunch() {
print
"进程启动失败 \n";
}

/**
* 子进程完成任务时的通知方法。
*/
function childProcessComplete($pid) {
print
"子进程" $pid" 已于 " . date('l jS \of F Y h:i:s A') . " 完成处理 \n";
}

/**
* 创建新进程的方法。
*/
function createProcess($pname) {
$pid = pcntl_fork();

if (
$pid == -1) {
errorOnProcessLunch();
}
else if (
$pid === 0) {
startChildProcess($pname);
exit();
// 确保退出。
}
else {
startParentProcess($pid);
}

}

/**
* 父线程执行时的通知方法。
*/
function startParentProcess($childProcessID){
print
"父线程创建子进程ID为" $childProcessID \n";
}

/**
* 所有子进程都已完成任务时的通知方法。
*/
function allChildProcessComplete(){
print
"所有子进程已完成处理 \n";
}

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

# Jeremy Brand <[email protected]>
# 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
{
# 由于我们是子进程,因此进行分叉,以便init成为我们的父进程。Init
# 很擅长等待子进程 - 也就是收割。
$cpid = pcntl_fork();
if (
$cpid == -1)
{
die(
"子进程无法创建子进程\n");
}
if (!
$cpid)
{
# 我们现在已经从我们的父进程中分叉出来,并且也不在等待任何其他
# 子进程来处理,但是其他子进程正在
# 同时运行。确保您在此处编写的代码在
# 此多进程环境中安全运行 - 例如:适当的锁定等。
# 在此处编写您要进行多进程处理的自定义代码。

# 在此处添加多进程代码
print "我们是子进程编号" $i\n";

# 不要忘记在子进程处理完成后退出。当然,如果您需要,请
# 更改此退出代码。
exit(0);
}
}
}

?>
2
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);
}

//-- accept,等待传入连接
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 类型的对象
}
1
Anonymous
20 年前
在示例 1 中,我发现除非我在 pcnt_signal 之前创建函数 sig_handler,否则它将无法工作.. 并且只会直接崩溃

(注意有这些问题的人)
1
cameronNO_SPAM at tripdubdev dot com
20 年前
我目前正在编写一些这方面的代码,但以防我忘记回来发布到论坛,或者以防这需要一段时间,为什么不运行一个单独的后台作业(通过 shell 启动)来跟踪哪些套接字可供客户端使用?然后,您所要做的就是与在后台运行的一个作业(或者它自己的迷你服务器)进行通信,该作业保持一个可供服务器使用的套接字数组。这似乎是最自然的替代方案,因为 PHP 声明不应在 Web 服务器环境中使用进程控制功能。我不想构建一个服务器,尤其是流量很大的服务器,它必须遍历一个循环才能找到一个可用的套接字。
1
schst at php dot net
20 年前
要消除子进程终止时的僵尸进程,您不必编写大量使用消息队列等复杂内容的代码。
相反,您只需设置一个信号处理程序

pcntl_signal(SIGCHLD, SIG_IGN);

Stephan
1
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 "Caught " . sig_identify($signo) . " (" . $signo . ") on " . 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");

我注释掉了 SIGNIT,因为它是在你按下 CTRL-C 时发送到你的进程的信号。如果你捕获了这个信号,你必须正确地处理它。

function sig_handler($signo) {
switch($signo) {
case SIGINT
// 自定义清理代码
exit; // 现在退出
break;
}
}

否则,停止进程的唯一方法是发送 SIGKILL 信号 - 你可以在 shell 中通过输入 "kill -9 PID" 来实现(其中 -9 是 SIGKILL 的数值)。

注意:你无法为 SIGSTOP 和 SIGKILL 添加处理程序(例如忽略信号) - 这是有充分理由的。
0
m dot quinton at gmail dot com
16 年前
一个使用 pcntl_fork 管理任务的类框架的很好的例子。

<?php

/**
* 作者:Marc Quinton / 2008 年 4 月。
*
* 使用 pcntl_fork、pcntl_wait 的简单任务管理框架。
*
* - 在底部查看示例用法。
* - 你应该覆盖 Task 类(SleepingClass 是一个示例),并在使用 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) {
# 我们在父类中
$this->pid = $pid;
# echo "< in parent with pid {$his->pid}\n";
} else{
# 我们是子进程
$this->run();
}
}

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

# 当任务完成时调用(在父进程中)
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();

?>
0
flyingguillotine1968 at yahoo dot com
17 年前
好的,下面是处理子进程的方法:>

你想用 pfork 分叉你的主程序,让它退出,
让新的子进程成为主程序(父进程)。

示例伪代码
主程序开始;
....
如果 pfork 分叉子进程成功
那么退出;

// 现在你的新子进程控制着...
// 使用信号处理程序让它退出(可能是 sigkill 或 sigterm)
// 当这个新进程 pfork 分叉时,将新的子进程 PID 存储在一个
// 数组中。当你的进程捕获 sigterm 信号时
// 循环遍历其子进程 PID 的数组,向每个子进程
// 发送 sigkill,然后对它们调用 pwait 以等待它们退出。
// 这将确保任何子进程都能正常清理
// 然后.. 现在是关键...
// 将你的 sigterm 处理程序重置回其原始的默认处理程序,
// 然后nnnnnnnnnnnnn 触发 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;
} // end if
} while ($deadPID == 0);

} // end for

// 现在重置 sigterm...
$this->signalSet($sigNum,SIG_DFL);
// 现在触发 sigterm...
$this->sendSignal(SIGTERM,$this->myPID);

这将允许你的主进程及其所有子进程正常退出,不会留下僵尸进程或其他不良后果!
0
van[at]webfreshener[dot]com
21 年前
用 pfork 分叉你的 PHP 守护进程会导致它在退出时变成僵尸进程。

... 或者我曾在
FreeBSD (PHP4.2.x)
Debian (PHP4.3.0-dev)
Darwin (PHP4.3.0-dev)

上看到过这种情况。这已通过上面的示例代码和其他为评估而创建的脚本进行了测试。

似乎在你的 configure 中添加 --enable-sigchild 就可以解决这个问题。

希望这能节省一些头发的撕扯:]
-2
andy at cbeyond dot net
19 年前
假设你想要分叉子进程来处理数百个不同的目标(例如,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(
"创建子进程失败");
} else if (
$pid) {
// 父进程
} else {
// 子进程
echo "子进程编号 $i\n";
// 执行一些操作
sleep(15);
exit;
}
}
-1
corychristison at lavacube dot com
19 年前
与第一个评论中提到的方法相比,一种更简单的方法是使用 `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";

?>
-1
Patrice Levesque
21 年前
所以,你想创建多个子进程,并且不想有任何僵尸进程,是吗?

你可以使用 IPC 来实现这一点。每个创建的子进程都需要告诉它的父进程它何时应该被终止。所以,是的,僵尸进程会创建,但是父进程会偶尔清理掉它的子进程。代码

<?php
declare(ticks = 1);
// 创建一个 IPC 消息队列
$msgqueue = msg_get_queue(ftok("/tmp/php_msgqueue.stat", 'R'),0666 | IPC_CREAT);
// 循环创建 1000 个子进程
for ($c = 0; $c < 1000; $c++) {
// fork
$pcid = pcntl_fork();
if (
$pcid == -1) {
die(
"创建子进程失败!");
}
elseif (
$pcid) { // 父进程,查找僵尸子进程并终止它们
// 检查 IPC 消息队列是否有任何条目
$currentqueue = msg_stat_queue($msgqueue);
$n = $currentqueue['msg_qnum']; // 消息数量(要终止的子进程数量)
if ($n > 0) {
echo
"有 $n 个子进程要终止。\n";
for (
$i = 0; $i < $n; $i++) {
// 从 IPC 消息队列中弹出子进程的 PID
if (!msg_receive ($msgqueue, 1, $msg_type, 16384, $msg, true, 0, $msg_error)) {
echo
"MSG_RECV 错误: $errmsg \n"; // 发生错误
}
else {
pcntl_waitpid($msg, $tmpstat, 0); // 真正终止子进程。
};
};
};
}
else {
// 子进程!
if (!posix_setsid()) { die ("无法分离"); }; // 分离
echo "我是子进程编号 $c\n";
sleep(5); // 执行一些有用的操作
// 通过 IPC 告诉父进程我已经完成了:发送我的 PID
if (!msg_send($msgqueue, 1, posix_getpid(), true, true, $errmsg)) {
echo
"MSG_SEND 错误: $errmsg \n";
};
exit();
// 变成僵尸进程,直到父进程杀死我
};
};
?>
To Top