php多进程编程二

来源:互联网 发布:淘宝new balance 编辑:程序博客网 时间:2024/06/05 14:46
php在pcntl_fork()后生成的子进程(通常为僵尸进程)必须由pcntl_waitpid()函数进行资源释放。但在pcntl_waitpid()不一定释放的就是当前运行的进程,也可能是过去生成的僵尸进程(没有释放);也可能是并发时其它访问者的僵尸进程。但可以 使用posix_kill($cid, SIGTERM)在子进程结束时杀掉它。
子进程会自动复制父进程空间里的变量。
PHP多进程编程示例2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31<?php
//.....
//需要安装pcntl的php扩展,并加载它
if(function_exists("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{
   // 不支持多进程处理时的代码在这里
}
//.....
?>

如果不需要阻塞进程,而又想得到子进程的退出状态,则可以注释掉pcntl_wait($status)语句,或写成:
<?php
pcntl_wait($status,1);
//或
pcntl_wait($status, WNOHANG);

在上面的代码中,如果父进程退出(使用exit函数退出或redirect),则会导致子进程成为僵尸进程(会交给init进程控制),子进程不再执行。
僵 尸进程是指的父进程已经退出,而该进程dead之后没有进程接受,就成为僵尸进程.(zombie)进程。任何进程在退出前(使用exit退出)都会变成 僵尸进程(用于保存进程的状态等信息),然后由init进程接管。如果不及时回收僵尸进程,那么它在系统中就会占用一个进程表项,如果这种僵尸进程过多, 最后系统就没有可以用的进程表项,于是也无法再运行其它的程序。
预防僵尸进程有以下几种方法:
  • 父 进程通过wait和waitpid等函数使其等待子进程结束,然后再执行父进程中的代码,这会导致父进程挂起。上面的代码就是使用这种方式实现的,但在WEB环境下,它不适合子进程需要长时间运行的情况(会导致超时)。使用wait和waitpid方法使父进程自动回收其僵尸子进程(根据子进程的返回状 态),waitpid用于临控指定子进程,wait是对于所有子进程而言。
  • 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后,父进程会收到该信号,可以在handler中调用wait回收
  • 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD, SIG_IGN)通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收,并不再给父进程发送信号,例如:pcntl_signal(SIGCHLD, SIG_IGN);
    $pid = pcntl_fork();
    //....code

  • 还有一个技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程再fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收还要自己做。下面是一个例子:1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35#include "apue.h"
    #include

    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回收 清理这些父进程已退出的僵尸子进程。
    所以,上面的示例可以改成:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49<?php

//.....
//需要安装pcntl的php扩展,并加载它
if(function_exists("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{
   // 不支持多进程处理时的代码在这里
}
//.....
?>
怎样产生僵尸进程的

一 个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit,它 的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。在Linux进程的状态中,僵尸进程是非常特殊的一种,它已 经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵 尸进程不再占有任何内存空间。它需要它的父进程来为它收尸,如果他的父进程没安装SIGCHLD信号处理函数调用wait或waitpid()等待子进程 结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了,那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。但 是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。
任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的 阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是”Z”。如果父进程能及时  处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。
如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
另外,还可以写一个php文件,然后在以后台形式来运行它,例如
<?php
  //Action代码
  public function createAction(){
    //....
    //将args替换成要传给insertLargeData.php的参数,参数间用空格间隔
    system('php -f insertLargeData.php '. ' args ' . '&');
    $this->redirect('/');
  }
?>

然后在insertLargeData.php文件中做数据库操作。也可以用cronjob + php的方式实现大数据量的处理。
如果是在终端运行php命令,当终端关闭后,刚刚执行的命令也会被强制关闭,如果你想让其不受终端关闭的影响,可以使用nohup命令实现:
<?php
  //Action代码
  public function createAction(){
    //....
    //将args替换成要传给insertLargeData.php的参数,参数间用空格间隔
    system('nohup php -f insertLargeData.php '. ' args ' . '&');
    $this->redirect('/');
  }
?>
原创粉丝点击