@H_301_0@ PHP有一组进程控制函数(编译时需要–enable-pcntl与posix扩展),使得PHP能实现跟c一样的创建子进程、使用exec函数执行程序、处理信号等功能。
// 必须加载扩展
if (!function_exists("pcntl_fork")) {
die("pcntl extention is must !");
}
//总进程的数量
$totals = 3;
// 执行的脚本数量
$cmdArr = array();
// 执行的脚本数量的数组
for ($i = 0; $i < $totals; $i++) {
$cmdArr[] = array("path" => DIR . "/run.PHP",'pid' =>$i,'total' =>$totals);
}
/*
展开:$cmdArr
Array
(
[0] => Array
(
[path] => /var/www/html/company/pcntl/run.PHP
[pid] => 0
[total] => 3
)
[1] => Array
(
[path] => /var/www/html/company/pcntl/run.<a href="/tag/PHP/" target="_blank" class="keywords">PHP</a>
[pid] => 1
[total] => 3
)
[2] => Array
(
[path] => /var/www/html/company/pcntl/run.<a href="/tag/PHP/" target="_blank" class="keywords">PHP</a>
[pid] => 2
[total] => 3
)
)
*/
pcntl_signal(SIGCHLD,SIG_IGN); //如果父进程不关心子进程什么时候结束,子进程结束后,内核会回收。
foreach ($cmdArr as $cmd) {
$pid = pcntl_fork(); //创建子进程
//父进程和子进程都会执行下面代码
if ($pid == -1) {
//错误处理:创建子进程失败时返回-1.
die('could not fork');
} else if ($pid) {
//父进程会得到子进程号,所以这里是父进程执行的逻辑
//如果不需要阻塞进程,而又想得到子进程的退出状态,则可以注释掉pcntl_wait($status)语句,或写成:
pcntl_wait($status,WNOHANG); //等待子进程中断,防止子进程成为僵尸进程。
} else {
//子进程得到的$pid为0,所以这里是子进程执行的逻辑。
$path = $cmd["path"];
$pid = $cmd['pid'] ;
$total = $cmd['total'] ;
echo exec("/usr/bin/PHP {$path} {$pid} {$total}")."\n";
exit(0) ;
}
}
?>
常用的PCNTL函数 1. pcntl_alarm ( int $seconds )
设置一个$seconds秒后发送SIGALRM信号的计数器 @H_301_0@2. pcntl_signal ( int $signo,callback $handler [,bool $restart_syscalls ] )
为$signo设置一个处理该信号的回调函数。下面是一个隔5秒发送一个SIGALRM信号,并由signal_handler函数获取,然后打印一个“Caught SIGALRM”的例子: @H_301_0@print "Caught SIGALRM\n";
pcntl_alarm(5);
}
pcntl_signal(SIGALRM,"signal_handler",true);
pcntl_alarm(5);
for(;;) {
}
?>
3. pcntl_exec ( string $path [,array $args [,array $envs ]] )
在当前的进程空间中执行指定程序,类似于c中的exec族函数。所谓当前空间,即载入指定程序的代码覆盖掉当前进程的空间,执行完该程序进程即结束。 @H_301_0@pcntl_exec($pathtobin,$arg);
echo '123'; //不会执行到该行
?>
4. pcntl_fork ( void )
为当前进程创建一个子进程,并且先运行父进程,返回的是子进程的PID,肯定大于零。在父进程的代码中可以用 pcntl_wait(&$status)暂停父进程知道他的子进程有返回值。注意:父进程的阻塞同时会阻塞子进程。但是父进程的结束不影响子进程的运行。 父进程运行完了会接着运行子进程,这时子进程会从执行pcntl_fork()的那条语句开始执行(包括此函数),但是此时它返回的是零(代表这是一个子进程)。在子进程的代码块中最好有exit语句,即执行完子进程后立即就结束。否则它会又重头开始执行这个脚本的某些部分。 @H_301_0@注意两点:- 子进程最好有一个exit;语句,防止不必要的出错;
- pcntl_fork间最好不要有其它语句,例如:
5. pcntl_wait ( int &$status [,int $options ] )
阻塞当前进程,只到当前进程的一个子进程退出或者收到一个结束当前进程的信号。使用$status返回子进程的状态码,并可以指定第二个参数来说明是否以阻塞状态调用: 阻塞方式调用的,函数返回值为子进程的pid,如果没有子进程返回值为-1; 非阻塞方式调用,函数还可以在有子进程在运行但没有结束的子进程时返回0。 @H_301_0@6. pcntl_waitpid ( int $pid,int &$status [,int $options ] )
功能同pcntl_wait,区别为waitpid为等待指定pid的子进程。当pid为-1时pcntl_waitpid与pcntl_wait 一样。在pcntl_wait和pcntl_waitpid两个函数中的$status中存了子进程的状态信息,这个参数可以用于 pcntl_wifexited、pcntl_wifstopped、pcntl_wifsignaled、pcntl_wexitstatus、 pcntl_wtermsig、pcntl_wstopsig、pcntl_waitpid这些函数。 例如:7. pcntl_getpriority ([ int $pid [,int $process_identifier ]] )
取得进程的优先级,即nice值,默认为0,在我的测试环境的linux中(CentOS release 5.2 (Final)),优先级为-20到19,-20为优先级最高,19为最低。(手册中为-20到20)。 @H_301_0@8. pcntl_setpriority ( int $priority [,int $pid [,int $process_identifier ]] )
设置进程的优先级。 @H_301_0@9. posix_kill
可以给进程发送信号 @H_301_0@10. pcntl_singal
用来设置信号的回调函数 @H_301_0@当父进程退出时,子进程如何得知父进程的退出 当父进程退出时,子进程一般可以通过下面这两个比较简单的方法得知父进程已经退出这个消息: @H_301_0@当父进程退出时,会有一个INIT进程来领养这个子进程。这个INIT进程的进程号为1,所以子进程可以通过使用getppid()来取得当前父进程的pid。如果返回的是1,表明父进程已经变为INIT进程,则原进程已经推出。 使用kill函数,向原有的父进程发送空信号(kill(pid,0))。使用这个方法对某个进程的存在性进行检查,而不会真的发送信号。所以,如果这个函数返回-1表示父进程已经退出。 @H_301_0@除了上面的这两个方法外,还有一些实现上比较复杂的方法,比如建立管道或socket来进行时时的监控等等。 @H_301_0@PHP多进程采集数据的例子/**
- 设置子进程通信文件所在目录
- @var string
*/
private $tmp_path='/tmp/';
/**
- Signfork引擎主启动方法
- 1、判断$arg类型,类型为数组时将值传递给每个子进程;类型为数值型时,代表要创建的进程数.
- @param object $obj 执行对象
- @param string|array $arg 用于对象中的__fork方法所执行的参数
- 如:$arg,自动分解为:$obj->fork($arg[0])、$obj->fork($arg[1])...
- @return array 返回 array(子进程序列=>子进程执行结果);
*/
public function run($obj,$arg=1){
if(!method_exists($obj,'fork')){
exit("Method 'fork' not found!");
}
if(is_array($arg)){
$i=0;
foreach($arg as $key=>$val){
$spawns[$i]=$key;
$i++;
$this->spawn($obj,$key,$val);
}
$spawns['total']=$i;
}elseif($spawns=intval($arg)){
for($i = 0; $i < $spawns; $i++){
$this->spawn($obj,$i);
}
}else{
exit('Bad argument!');
}
if($i>1000) exit('Too many spawns!');
return $this->request($spawns);
}
/**
- Signfork主进程控制方法
- 1、$tmpfile 判断子进程文件是否存在,存在则子进程执行完毕,并读取内容
- 2、$data收集子进程运行结果及数据,并用于最终返回
- 3、删除子进程文件
- 4、轮询一次0.03秒,直到所有子进程执行完毕,清理子进程资源
- @param string|array $arg 用于对应每个子进程的ID
- @return array 返回 array([子进程序列]=>[子进程执行结果]);
*/
private function request($spawns){
$data=array();
$i=is_array($spawns)?$spawns['total']:$spawns;
for($ids = 0; $ids<$i; $ids++){
while(!($cid=pcntl_waitpid(-1,$status,WNOHANG)))usleep(30000);
$tmpfile=$this->tmppath.'sfpid'.$cid;
$data[$spawns['total']?$spawns[$ids]:$ids]=file_get_contents($tmpfile);
unlink($tmpfile);
}
return $data;
}
/**
- Signfork子进程执行方法
- 1、pcntl_fork 生成子进程
- 2、file_put_contents 将'$obj->__fork($val)'的执行结果存入特定序列命名的文本
- 3、posix_kill杀死当前进程
- @param object $obj 待执行的对象
- @param object $i 子进程的序列ID,以便于返回对应每个子进程数据
- @param object $param 用于输入对象$obj方法'__fork'执行参数
*/
private function spawn($obj,$i,$param=null){
if(pcntl_fork()===0){
$cid=getmypid();
file_put_contents($this->tmppath.'sfpid'.$cid,$obj->__fork($param));
posix_kill($cid,SIGTERM);
exit;
}
}
}
?>
<?php
pcntl_wait($status,1);
//或
pcntl_wait($status,WNOHANG);
?>
预防僵尸进程有以下几种方法:
@H_301_0@1. 父进程通过wait和waitpid等函数使其等待子进程结束,然后再执行父进程中的代码,这会导致父进程挂起。上面的代码就是使用这种方式实现的,但在WEB环境下,它不适合子进程需要长时间运行的情况(会导致超时)。 使用wait和waitpid方法使父进程自动回收其僵尸子进程(根据子进程的返回状态),waitpid用于临控指定子进程,wait是对于所有子进程而言。 2. 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后,父进程会收到该信号,可以在handler中调用wait回收 3. 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN)通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收,并不再给父进程发送信号,例如:int main(void){
pid_t pid;
if ((pid = fork()) < 0){
err_sys("fork error");
} else if (pid == 0){ /// first child /
if ((pid = fork()) < 0){
err_sys("fork error");
}elseif(pid > 0){
exit(0); /// parent from second fork == first child /
}
/**
- We're the second child; our parent becomes init as soon
- as our real parent calls exit() in the statement above.
- Here's where we'd continue executing,knowing that when
- we're done,init will reap our status.
*/
sleep(2);
printf("second child,parent pid = %d ",getppid());
exit(0);
}
if (waitpid(pid,NULL,0) != pid) /*// wait for first child */
err_sys("waitpid error");
/**
- We're the parent (the original process); we continue executing, knowing that we're not the parent of the second child.
/
exit(0);
}