PHP Conference Japan 2024

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

// 启动守护进程!
System_Daemon::start();

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

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

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

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

此脚本将与一个从终端分离并由 init 收割的父进程一起无限循环,并创建五个名为 1 到 5 的子进程。当一个子进程在数据库中看到它的名称(只有一个字段“test”的表“test.tb”)时,它会自行结束。要终止子进程,请将其值插入数据库中。父进程只有在所有子进程都结束时才会结束。

多么悲伤但有趣的故事……

$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();
}
kementeusNOSPAM at gmail dot com
17 年前
在文档示例中,我们需要将 pcntl_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. 启动程序,然后派生以从终端分离(杀死父进程并将子进程设为会话领导者)。

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

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

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

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

我必须在下个注释中发布代码,此网站上的注释引擎不允许发布这么长的注释,但我认为此代码示例非常值得评论……
[email protected]
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
[email protected]
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
"在进程ID " .getmypid()." 上启动 Job1,时间为 " . date('l jS \of F Y h:i:s A') . ",预计执行时间为 $executionTime 秒\n";
}else if(
$childProcessName === "Job2"){
print
"在进程ID " .getmypid()." 上启动 Job2,时间为 " . 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";
}

?>
[email protected]
21 年前
#!/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);
}
}
}

?>
keksov[at]gmx.de
22年前
您必须在 socket_accept 之前使用 socket_select,以便您的代码等待 select 连接。socket_select很容易被信号中断。以下是我的库中的一个示例(TNetSocket类的method)
//-- 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 类型的新的对象
}
匿名用户
21 年前
在示例 1 中,我发现除非我在 pcnt_signal 之前创建 sig_handler 函数,否则它不会工作……只会崩溃。

(给遇到此类问题的用户的一个提示)
[email protected]
21 年前
我目前正在为此编写一些代码,但是以防我忘记回复到论坛,或者以防需要一段时间,为什么不只运行一个单独的后台作业(通过 shell 启动)来跟踪哪些套接字可供客户端使用?然后,您只需与在后台运行的一个作业(或它自己的小型服务器)通信,该作业保留服务器可用套接字的数组。这似乎是最自然的替代方案,因为 PHP 声称不应在 Web 服务器环境中使用进程控制功能。我不想构建一个服务器,尤其是一个流量很大的服务器,它必须循环遍历才能找到一个可用的套接字。
[email protected]
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 "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");

我注释掉了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

/**
* 作者: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();

?>
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;
} // end if
} while ($deadPID == 0);

} // end for

// 现在重置 sigterm …
$this->signalSet($sigNum,SIG_DFL);
// 现在恢复 sigterm …
$this->sendSignal(SIGTERM,$this->myPID);

这将允许您的主进程及其所有子进程正确退出,而不会留下僵尸进程或其他不良内容!
van[at]webfreshener[dot]com
22年前
分叉您的PHP守护进程会导致它在退出时变成僵尸进程。

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

使用以上示例代码和其他为评估创建的脚本进行了测试。

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

希望这能省去你一些扯头发的烦恼 :]
andy at cbeyond dot net
20年前
假设你想派生子进程来处理几百个不同的目标(例如,SNMP轮询,但这只是一个例子)。因为你不想让系统因为创建大量子进程而崩溃,这里有一种限制任何时候正在运行的子进程数量的方法。

#!/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;
}
}
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);
// 创建一个 IPC 消息队列
$msgqueue = msg_get_queue(ftok("/tmp/php_msgqueue.stat", 'R'),0666 | IPC_CREAT);
// 循环创建 1000 个子进程
for ($c = 0; $c < 1000; $c++) {
// 派生子进程
$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