`

php中实现多进程

    博客分类:
  • PHP
 
阅读更多

使用PHP真正的多进程运行模式,适用于数据采集、邮件群发、数据源更新、tcp服务器等环节。

PHP有一组进程控制函数(编译时需要 –enable-pcntl与posix扩展),使得php能在*nix系统中实现跟c一样的创建子进程、使用exec函数执行程序、处理信号等功能。 PCNTL使用ticks来作为信号处理机制(signal handle callback mechanism),可以最小程度地降低处理异步事件时的负载。何谓ticks?Tick 是一个在代码段中解释器每执行 N 条低级语句就会发生的事件,这个代码段需要通过declare来指定。

常用的PCNTL函数
1. pcntl_alarm ( int $seconds )
设置一个$seconds秒后发送SIGALRM信号的计数器

2. pcntl_signal ( int $signo , callback $handler [, bool $restart_syscalls ] )
为$signo设置一个处理该信号的回调函数。下面是一个隔5秒发送一个SIGALRM信号,并由signal_handler函数获取,然后打印一个“Caught SIGALRM”的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
declare(ticks = 1);  
  
function signal_handler($signal) {
    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族函数。所谓当前空间,即载入指定程序的代码覆盖掉当前进程的空间,执行完该程序进程即结束。

1
2
3
4
5
6
7
8
9
10
11
<?php
$dir = '/home/shankka/';
$cmd = 'ls';
$option = '-l';
$pathtobin = '/bin/ls';  
  
$arg = array($cmd, $option, $dir);  
  
pcntl_exec($pathtobin, $arg);
echo '123';    //不会执行到该行
?>

4. pcntl_fork ( void )
为当前进程创建一个子进程,并且先运行父进程,返回的是子进程的PID,肯定大于零。在父进程的代码中可以用 pcntl_wait(&$status)暂停父进程知道他的子进程有返回值。注意:父进程的阻塞同时会阻塞子进程。但是父进程的结束不影响子进程的运行。
父进程运行完了会接着运行子进程,这时子进程会从执行pcntl_fork()的那条语句开始执行(包括此函数),但是此时它返回的是零(代表这是一个子进程)。在子进程的代码块中最好有exit语句,即执行完子进程后立即就结束。否则它会又重头开始执行这个脚本的某些部分。

注意两点:
1. 子进程最好有一个exit;语句,防止不必要的出错;
2. pcntl_fork间最好不要有其它语句,例如:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$pid = pcntl_fork();
//这里最好不要有其他的语句
if ($pid == -1) {
    die('could not fork');
} else if ($pid) {
    // we are the parent
pcntl_wait($status); //Protect against Zombie children
} else {
    // we are the child
}
?>

5. pcntl_wait ( int &$status [, int $options ] )
阻塞当前进程,只到当前进程的一个子进程退出或者收到一个结束当前进程的信号。使用$status返回子进程的状态码,并可以指定第二个参数来说明是否以阻塞状态调用:
1. 阻塞方式调用的,函数返回值为子进程的pid,如果没有子进程返回值为-1;
2. 非阻塞方式调用,函数还可以在有子进程在运行但没有结束的子进程时返回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这些函数。
例如:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$pid = pcntl_fork();
if($pid) {
    pcntl_wait($status);
    $id = getmypid();
    echo "parent process,pid {$id}, child pid {$pid}\n";
}else{
    $id = getmypid();
    echo "child process,pid {$id}\n";
    sleep(2);
}
?>

子进程在输出child process等字样之后sleep了2秒才结束,而父进程阻塞着直到子进程退出之后才继续运行。

7. pcntl_getpriority ([ int $pid [, int $process_identifier ]] )
取得进程的优先级,即nice值,默认为0,在我的测试环境的linux中(CentOS release 5.2 (Final)),优先级为-20到19,-20为优先级最高,19为最低。(手册中为-20到20)。

8. pcntl_setpriority ( int $priority [, int $pid [, int $process_identifier ]] )
设置进程的优先级。

9. posix_kill
可以给进程发送信号

10. pcntl_singal
用来设置信号的回调函数

当父进程退出时,子进程如何得知父进程的退出
当父进程退出时,子进程一般可以通过下面这两个比较简单的方法得知父进程已经退出这个消息:

1. 当父进程退出时,会有一个INIT进程来领养这个子进程。这个INIT进程的进程号为1,所以子进程可以通过使用getppid()来取得当前父进程的pid。如果返回的是1,表明父进程已经变为INIT进程,则原进程已经推出。
2. 使用kill函数,向原有的父进程发送空信号(kill(pid, 0))。使用这个方法对某个进程的存在性进行检查,而不会真的发送信号。所以,如果这个函数返回-1表示父进程已经退出。

除了上面的这两个方法外,还有一些实现上比较复杂的方法,比如建立管道或socket来进行时时的监控等等。

PHP多进程采集数据的例子

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<?php
/**
* Project: Signfork: php多线程库
* File:    Signfork.class.php
*/
  
class Signfork{
  /**
   * 设置子进程通信文件所在目录
   * @var string
   */
  private $tmp_path='/tmp/';
  
/**
  * Signfork引擎主启动方法
  * 1、判断$arg类型,类型为数组时将值传递给每个子进程;类型为数值型时,代表要创建的进程数.
  * @param object $obj 执行对象
  * @param string&#124;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&#124;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->tmp_path.'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->tmp_path.'sfpid_'.$cid,$obj->__fork($param));
      posix_kill($cid, SIGTERM);
      exit;
    }
  }
}
?>

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)语句,或写成:

1
2
3
4
5
<?php
pcntl_wait($status, 1);
//或
pcntl_wait($status, WNOHANG);
?>

在上面的代码中,如果父进程退出(使用exit函数退出或redirect),则会导致子进程成为僵尸进程(会交给init进程控制),子进程不再执行。

僵尸进程是指的父进程已经退出,而该进程dead之后没有进程接受,就成为僵尸进程.(zombie)进程。任何进程在退出前(使用exit退出) 都会变成僵尸进程(用于保存进程的状态等信息),然后由init进程接管。如果不及时回收僵尸进程,那么它在系统中就会占用一个进程表项,如果这种僵尸进程过多,最后系统就没有可以用的进程表项,于是也无法再运行其它的程序。

预防僵尸进程有以下几种方法:

1. 父进程通过wait和waitpid等函数使其等待子进程结束,然后再执行父进程中的代码,这会导致父进程挂起。上面的代码就是使用这种方式实现的,但在WEB环境下,它不适合子进程需要长时间运行的情况(会导致超时)。
使用wait和waitpid方法使父进程自动回收其僵尸子进程(根据子进程的返回状态),waitpid用于临控指定子进程,wait是对于所有子进程而言。
2. 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后,父进程会收到该信号,可以在handler中调用wait回收
3. 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD, SIG_IGN)通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收,并不再给父进程发送信号,例如:

1
2
3
4
5
<?php
pcntl_signal(SIGCHLD, SIG_IGN);
$pid = pcntl_fork();
//....code
?>

4. 还有一个技巧,就是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 <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回收清理这些父进程已退出的僵尸子进程。

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

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文件,然后在以后台形式来运行它,例如:

1
2
3
4
5
6
7
8
9
<?php
//Action代码
public function createAction(){
    //....
    //将args替换成要传给insertLargeData.php的参数,参数间用空格间隔
    system('php -f insertLargeData.php ' . ' args ' . '&');
    $this->redirect('/');
}
?>

然后在insertLargeData.php文件中做数据库操作。也可以用cronjob + php的方式实现大数据量的处理。

如果是在终端运行php命令,当终端关闭后,刚刚执行的命令也会被强制关闭,如果你想让其不受终端关闭的影响,可以使用nohup命令实现:

1
2
3
4
5
6
7
8
9
<?php
//Action代码
public function createAction(){
    //....
    //将args替换成要传给insertLargeData.php的参数,参数间用空格间隔
    system('nohup php -f insertLargeData.php ' . ' args ' . '&');
    $this->redirect('/');
}
?>

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

 

(转自 http://www.hdj.me/php-multithread

分享到:
评论

相关推荐

    PHP-多进程编程示例代码

    在PHP中,可以借助`pcntl`(Process Control)扩展来实现多进程编程。`pcntl`扩展提供了诸如创建子进程、进程间通信、信号处理等功能,使得PHP具备了进行复杂并发任务的能力。 标题"PHP-多进程编程示例代码"涉及到...

    EasyTask简单易用的PHP常驻内存多进程任务管理器.zip

    它简化了在PHP中实现多进程编程的复杂性,使得开发者可以专注于业务逻辑,而非底层的进程管理。在实际项目中,EasyTask可以与其他PHP框架(如Laravel、Symfony等)结合使用,提升整个系统的处理能力和响应速度。

    PHP实现多线程多进程

    ### PHP 实现多线程与多进程 #### 知识点概述 在 PHP 中实现多线程或多进程是一项挑战性的任务,因为 PHP 本质上是一种单线程语言,并且主要用于 Web 开发,通常运行在共享内存环境中。然而,在某些场景下,如后台...

    PHP多进程实例讲解

    PHP多进程实例讲解,一个PHP,如何实现PHP多进程,并控制进程数量

    PHP多进程编程实例

    由于PHP是一种主要用于Web开发的编程语言,它本身并不擅长处理多进程任务,所以在PHP中实现多进程编程需要借助特定的扩展。pcntl(Process Control)扩展是PHP提供的一套用于管理进程的扩展模块,它允许PHP脚本创建...

    Php多进程实现代码

    本篇文章将详细介绍如何在PHP中实现多进程,并提供一个简单的实例。 PHP提供了`pcntl`(Process Control)扩展,允许我们在PHP程序中创建和管理子进程。不过需要注意的是,`pcntl`扩展并不是PHP的标准配置,通常...

    php swoole 多进程发送邮件

    结合Swoole的多进程特性,可以将邮件列表分发到各个子进程中,每个子进程独立发送自己的邮件,这样能大幅提高邮件发送速率。 4. 示例代码结构: - 主进程负责创建子进程,分配邮件任务。 - 子进程接收任务后,...

    PHP实现多进程并行操作的详解(可做守护进程)

    在PHP中实现多进程并行操作是提升程序执行效率的重要手段,特别是在处理大量并发任务时。本文将详细讲解如何在PHP中使用`pcntl_fork`函数创建子进程,实现多进程并行处理,并讨论如何将这些进程设置为守护进程。 ...

    Redis延时消息队列基于swoole实现的多进程消费端

    标题中的“Redis延时消息队列基于swoole实现的多进程消费端”是指使用Redis作为消息队列,结合Swoole的多进程特性来构建一个高效、可扩展的延迟消息处理系统。在这个系统中,Redis作为一个可靠的键值存储,用于暂存...

    php+html5多进程实现超大附件无刷新上传

    PHP可以通过pthreads扩展实现多线程,或者使用子进程(如proc_open()函数)来并发处理文件片段的接收和合并。这种方式可以显著减少大文件上传的总耗时,尤其在网络条件不佳的情况下。 此外,考虑到上传进度的反馈,...

    php实现多进程下载文件类

    使用curl的批处理方法,开启多进程,实现批量下载文件。批量下载文件一般使用循环的方式,逐一执行下载。但在带宽与服务器性能允许的情况下,使用多进程进行...本类利用curl的多进程请求方法,实现多进程同时下载文件。

    一个纯PHP实现的多进程,定时任务管理工具,兼容部分crontab语法, 支持守护进程

    这个工具的核心特性是其纯PHP实现,这意味着它可以在任何支持PHP的环境中运行,极大地提高了跨平台的兼容性。 在系统设计上,cronManager采用了多进程模型,这使得它能够同时处理多个任务,提高任务执行的并发性和...

    PHP的一个面向对象的多进程管理器

    PHP实现多进程主要依赖于`pcntl`(Process Control)扩展。这个扩展提供了一系列函数,如`pcntl_fork()`用于创建子进程,`pcntl_wait()`和`pcntl_waitpid()`用于等待子进程结束,以及`pcntl_signal()`用于处理信号。...

    php中实现进程锁与多进程的方法

    PHP中实现进程锁与多进程的方法涵盖了进程锁的基本概念、在单进程和多进程中的使用,以及在Yii2框架中的具体实现和如何杀死进程。下面将详细阐述这些知识点。 首先,进程锁的主要作用是防止程序的重复执行,尤其...

    php多进程中的阻塞与非阻塞操作实例分析

    总结来说,在PHP中实现多进程任务时,了解阻塞与非阻塞操作的区别及其适用场景是非常重要的。合理地利用这些技术可以显著提升应用程序的性能和用户体验。随着对PHP多进程和并行处理能力的深入理解,开发者可以编写出...

    php 的多进程操作实践案例分析

    PHP的多进程操作通过`pcntl`扩展实现,并提供了`pcntl_fork()`、`pcntl_wait()`、`pcntl_waitpid()`等函数来控制进程的创建、同步和回收。在实际应用中,需要注意父进程与子进程的执行独立性、进程同步以及防止僵尸...

    php开启多进程的方法

    在PHP中实现多进程可以极大地提升程序的性能和效率,尤其是在处理耗时或者并行任务时。以下内容将围绕如何利用PHP的多进程技术进行深入讲解。 首先,PHP本身并不是一种支持多线程的语言,而更倾向于使用多进程模型...

    multiprocess可轻松让普通PHP脚本变守护进程和多进程执行

    标题中的“multiprocess”指的是在PHP环境中实现多进程处理的技术,这在CLI(命令行界面)环境下特别有用。多进程是指一个程序同时运行多个独立的子进程,每个子进程都有自己的内存空间,可以并行执行任务,从而提高...

Global site tag (gtag.js) - Google Analytics