网络编程 
首页 > 网络编程 > 浏览文章

以实例全面讲解PHP中多进程编程的相关函数的使用

(编辑:jimmy 日期: 2024/5/31 浏览:3 次 )

    PHP有一组进程控制函数(编译时需要–enable-pcntl与posix扩展),使得php能实现跟c一样的创建子进程、使用exec函数执行程序、处理信号等功能。
   

<"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.php 
        [pid] => 1 
        [total] => 3 
      ) 
   
    [2] => Array 
      ( 
        [path] => /var/www/html/company/pcntl/run.php 
        [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) ; 
    } 
  } 
  "htmlcode">
<"Caught SIGALRM\n";
  pcntl_alarm(5);
}
 
pcntl_signal(SIGALRM, "signal_handler", true);
pcntl_alarm(5);
 
for(;;) {
}
 
"htmlcode">
<"htmlcode">
<"htmlcode">
<"parent process,pid {$id}, child pid {$pid}\n";
}else{
  $id = getmypid();
  echo "child process,pid {$id}\n";
  sleep(2);
}
"htmlcode">
<"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&#124;array $arg 用于对应每个子进程的ID
  * @return array 返回  array([子进程序列]=>[子进程执行结果]);
  */
  private function request($spawns){
   $data=array();
   $i=is_array($spawns)"htmlcode">
<"pcntl_fork")){
  //生成子进程
 $pid = pcntl_fork();
 if($pid == -1){
  die('could not fork');
 }else{
  if($pid){
   $status = 0;
   //阻塞父进程,直到子进程结束,不适合需要长时间运行的脚本,可使用pcntl_wait($status, 0)实现非阻塞式
   pcntl_wait($status);
   // parent proc code
   exit;
  }else{
   // child proc code
   //结束当前子进程,以防止生成僵尸进程
   if(function_exists("posix_kill")){
    posix_kill(getmypid(), SIGTERM);
   }else{
    system('kill -9'. getmypid());
   }
   exit;
  }
 }
}else{
  // 不支持多进程处理时的代码在这里
}
//.....
"htmlcode">
<"htmlcode">
#include "apue.h"
#include <sys/wait.h>
 
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);
}

在fork()/execve()过程中,假设子进程结束时父进程仍存在,而父进程fork()之前既没安装SIGCHLD信号处理函数调用 waitpid()等待子进程结束,又没有显式忽略该信号,则子进程成为僵尸进程,无法正常结束,此时即使是root身份kill-9也不能杀死僵尸进程。补救办法是杀死僵尸进程的父进程(僵尸进程的父进程必然存在),僵尸进程成为”孤儿进程”,过继给1号进程init,init会定期调用wait回收清理这些父进程已退出的僵尸子进程。

所以,上面的示例可以改成:
 

<"pcntl_fork")){
 //生成第一个子进程
$pid = pcntl_fork(); //$pid即所产生的子进程id
if($pid == -1){
 //子进程fork失败
 die('could not fork');
}else{
 if($pid){
  //父进程code
  sleep(5); //等待5秒
  exit(0); //或$this->_redirect('/');
 }else{
  //第一个子进程code
  //产生孙进程
  if(($gpid = pcntl_fork()) < 0){ ////$gpid即所产生的孙进程id
   //孙进程产生失败
   die('could not fork');
  }elseif($gpid > 0){
   //第一个子进程code,即孙进程的父进程
   $status = 0;
   $status = pcntl_wait($status); //阻塞子进程,并返回孙进程的退出状态,用于检查是否正常退出
   if($status ! = 0) file_put_content('filename', '孙进程异常退出');
   //得到父进程id
   //$ppid = posix_getppid(); //如果$ppid为1则表示其父进程已变为init进程,原父进程已退出
   //得到子进程id:posix_getpid()或getmypid()或是fork返回的变量$pid
   //kill掉子进程
   //posix_kill(getmypid(), SIGTERM);
   exit(0);
  }else{ //即$gpid == 0
   //孙进程code
   //....
   //结束孙进程(即当前进程),以防止生成僵尸进程
   if(function_exists('posix_kill')){
     posix_kill(getmypid(), SIGTERM);
   }else{
     system('kill -9'. getmypid());
   }
   exit(0);
  }
 }
}
}else{
 // 不支持多进程处理时的代码在这里
}
//.....
"htmlcode">
<"htmlcode">
<?php
//Action代码
public function createAction(){
  //....
  //将args替换成要传给insertLargeData.php的参数,参数间用空格间隔
  system('nohup php -f insertLargeData.php ' . ' args ' . '&');
  $this->redirect('/');
}
?>

你还可以使用screen命令代替nohup命令。

上一篇:php生成图片验证码-附五种验证码
下一篇:深入探究PHP的多进程编程方法