关于php多进程编程

1.关于多进程的基础知识:http://www.cnblogs.com/Anker/p/3271773.html

2.php变成的三种方法:http://www.jb51.net/article/71232.htm

子进程的创建
一般的子进程的写法是:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$pid = pcntl_fork();
if ( $pid == -1){
      //创建失败
      die ( 'could not fork' );
}
else {
     if ( $pid ){
         //从这里开始写的代码是父进程的
         exit ( "parent!" );
     }
     else {
         //子进程代码,为防止不停的启用子进程造成系统资源被耗尽的情况,一般子进程代码运行完成后,加入exit来确保子进程正常退出。
         exit ( "child" );
     }
}
?>

    上边的代码如果创建子进程成功的话,系统就有了2个进程,一个为父进程,一个为子进程,子进程的id号为$pid。在系统运行到$pid = pcntl_fork();时,在这个地方进行分支,父子进程各自开始运行各自的程序代码。代码的运行结果是parent 和child,很奇怪吧,为什么一个if和else互斥的代码中,都输出了结果?其实是像上边所说的,代码在pcntl_fork时,一个父进程运行parent,一个子进程运行了child。在代码结果上就显示了parent和child。至于谁先谁后的问题,这得要看系统资源的分配了。

    如果需要起多个进程来处理数据,可以根据数据的数量,按照约定好的数量比如说1000条一个进程来起子进程。使用for循环就可以了。   

?
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
#如果获得的总数小于或等于0,等待60秒,并退出
  if ( $count <= 0)
  {
    sleep(60);
    exit ;
  }
  #如果大于1000,计算需要起的进程数
  if ( $count > 1000)
  {
    $cycleSize = ceil ( $count /1000);
  }
  else
  {
    $cycleSize = 1;
  }
  
  for ( $i =0; $i < $cycleSize ; $i ++)
  {
    $pid  = pcntl_fork();
    if ( $pid == -1)
    {
      break ;
    }
    else
    {
      if ( $pid )
      {
        #父进程获得子进程的pid,存入数组
        $pidArr [] = $pid ;
      }
      else
      {
        //开始发送,子进程执行完自己的任务后,退出。
          exit ;
      }
    }
  }
  
  while ( count ( $pidArr ) > 0)
  {
    $myId  = pcntl_waitpid(-1, $status , WNOHANG);
    foreach ( $pidArr as $key => $pid )
    {
      if ( $myId == $pid ) unset( $pidArr [ $key ]);
    }
  }

    然后使用crontab,来使此PHP程序每隔一段时间自动执行。

    当然,示例代码比较简单,具体还需要考虑怎么防止多个子进程执行到同一条数据或者当前进程处理数据未完成时,crontab又开始执行PHP文件启用新的进程等等。


PHP多进程实现方式
下面来系统地整理一下PHP多进程的实现方式:

1. 直接方式

pcntl_fork() 创建一个进程,在父进程返回值是子进程的pid,在子进程返回值是0,-1表示创建进程失败。跟C非常相似。

测试脚本 test.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
<?php
   // example of multiple processes
   date_default_timezone_set( 'Asia/Chongqing' );
   echo "parent start, pid " , getmypid (), "\n" ;
   beep();
   for ( $i =0; $i <3; ++ $i ){
      $pid = pcntl_fork();
       if ( $pid == -1){
          die ( "cannot fork" );
      } else if ( $pid > 0){
          echo "parent continue \n" ;
          for ( $k =0; $k <2; ++ $k ){
            beep();
         }
      } else if ( $pid == 0){
          echo "child start, pid " , getmypid (), "\n" ;
          for ( $j =0; $j <5; ++ $j ){
            beep();
         }
          exit ;
      }
   }
   // ***
   function beep(){
       echo getmypid (), "\t" , date ( 'Y-m-d H:i:s' , time()), "\n" ;
      sleep(1);
   }
?>

用命令行运行

?
1
#php -f test.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
parent start, pid 1793
1793  2013-01-14 15:04:17
parent continue
1793  2013-01-14 15:04:18
child start, pid 1794
1794  2013-01-14 15:04:18
1794  2013-01-14 15:04:19
1793  2013-01-14 15:04:19
1794  2013-01-14 15:04:20
parent continue
1793  2013-01-14 15:04:20
child start, pid 1795
1795  2013-01-14 15:04:20
17931794        2013-01-14 15:04:212013-01-14 15:04:21
 
1795  2013-01-14 15:04:21
1794  2013-01-14 15:04:22
1795  2013-01-14 15:04:22
parent continue
1793  2013-01-14 15:04:22
child start, pid 1796
1796  2013-01-14 15:04:22
1793  2013-01-14 15:04:23
1796  2013-01-14 15:04:23
1795  2013-01-14 15:04:23
1795  2013-01-14 15:04:24
1796  2013-01-14 15:04:24
1796  2013-01-14 15:04:25
1796  2013-01-14 15:04:26

从中看到,创建了3个子进程,和父进程一起并行运行。其中有一行格式跟其他有些不同,
17931794                2013-01-14 15:04:212013-01-14 15:04:21
因为两个进程同时进行写操作,造成了冲突。


2. 阻塞方式

用直接方式,父进程创建了子进程后,并没有等待子进程结束,而是继续运行。似乎这里看不到有什么问题。如果php脚本并不是运行完后自动结束,而是常驻内存的,就会造成子进程无法回收的问题。也就是僵尸进程。可以通过pcntl_wai()方法等待进程结束,然后回收已经结束的进程。
将测试脚本改成:
 

?
1
2
3
4
5
6
7
8
9
10
11
12
$pid = pcntl_fork();
if ( $pid == -1){
   ...
} else if ( $pid > 0){
    echo "parent continue \n" ;
    pcntl_wait( $status );
    for ( $k =0; $k <2; ++ $k ){
      beep();
   }
} else if ( $pid == 0){
    ...
}

用命令行运行

?
1
#php -f test.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
parent start, pid 1807
1807  2013-01-14 15:20:05
parent continue
child start, pid 1808
1808  2013-01-14 15:20:06
1808  2013-01-14 15:20:07
1808  2013-01-14 15:20:08
1808  2013-01-14 15:20:09
1808  2013-01-14 15:20:10
1807  2013-01-14 15:20:11
1807  2013-01-14 15:20:12
parent continue
child start, pid 1809
1809  2013-01-14 15:20:13
1809  2013-01-14 15:20:14
1809  2013-01-14 15:20:15
1809  2013-01-14 15:20:16
1809  2013-01-14 15:20:17
1807  2013-01-14 15:20:18
1807  2013-01-14 15:20:19
child start, pid 1810
1810  2013-01-14 15:20:20
parent continue
1810  2013-01-14 15:20:21
1810  2013-01-14 15:20:22
1810  2013-01-14 15:20:23
1810  2013-01-14 15:20:24
1807  2013-01-14 15:20:25
1807  2013-01-14 15:20:26

父进程在pcntl_wait()将自己阻塞,等待子进程运行完了才接着运行。


3. 非阻塞方式

阻塞方式失去了多进程的并行性。还有一种方法,既可以回收已经结束的子进程,又可以并行。这就是非阻塞的方式。
修改脚本:
 

?
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
<?php
   // example of multiple processes
   date_default_timezone_set( 'Asia/Chongqing' );
   declare (ticks = 1);
   pcntl_signal(SIGCHLD, "garbage" );
   echo "parent start, pid " , getmypid (), "\n" ;
   beep();
   for ( $i =0; $i <3; ++ $i ){
      $pid = pcntl_fork();
       if ( $pid == -1){
          die ( "cannot fork" );
      } else if ( $pid > 0){
          echo "parent continue \n" ;
          for ( $k =0; $k <2; ++ $k ){
            beep();
         }
      } else if ( $pid == 0){
          echo "child start, pid " , getmypid (), "\n" ;
          for ( $j =0; $j <5; ++ $j ){
            beep();
         }
          exit (0);
      }
   }
   // parent
   while (1){
       // do something else
      sleep(5);
   }
   // ***
   function garbage( $signal ){
       echo "signel $signal received\n" ;
       
       while (( $pid = pcntl_waitpid(-1, $status , WNOHANG))> 0){
          echo "\t child end pid $pid , status $status\n" ;
      }
   }
   function beep(){
       echo getmypid (), "\t" , date ( 'Y-m-d H:i:s' , time()), "\n" ;
      sleep(1);
   }
?>

用命令行运行

?
1
#php -f test.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
parent start, pid 2066
2066  2013-01-14 16:45:34
parent continue
2066  2013-01-14 16:45:35
child start, pid 2067
2067  2013-01-14 16:45:35
20662067        2013-01-14 16:45:362013-01-14 16:45:36
 
2067  2013-01-14 16:45:37
parent continue
2066  2013-01-14 16:45:37
child start, pid 2068
2068  2013-01-14 16:45:37
2067  2013-01-14 16:45:38
2068  2013-01-14 16:45:38
2066  2013-01-14 16:45:38
parent continue
2066  2013-01-14 16:45:40
child start, pid 2069
2069  2067  2013-01-14 16:45:40
2013-01-14 16:45:40
2068  2013-01-14 16:45:40
2066  2013-01-14 16:45:41
2069  2013-01-14 16:45:41
2068  2013-01-14 16:45:41
signel 17 received
      child end pid 2067, status 0
2069  2013-01-14 16:45:42
2068  2013-01-14 16:45:42
2069  2013-01-14 16:45:43
signel 17 received
      child end pid 2068, status 0
2069  2013-01-14 16:45:44
signel 17 received
      child end pid 2069, status 0

多个进程又并行运行了,而且运行大约10秒钟之后,用 ps -ef | grep php 查看正在运行的进程,只有一个进程
lqling    2066  1388  0 16:45 pts/1    00:00:00 php -f t5.php
是父进程,子进程被回收了。


子进程退出状态

?
1
pcntl_waitpid(-1, $status , WNOHANG) $status

 返回子进程的结束状态


windows下多线程

windows系统不支持pcntl函数,幸好有curl_multi_exec()这个工具,利用内部的多线程,访问多个链接,每个链接可以作为一个任务。

编写脚本 test1.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
<?php
   date_default_timezone_set( 'Asia/Chongqing' );
   $tasks = array (
   );
   $mh = curl_multi_init();
   foreach ( $tasks as $i => $task ){
      $ch [ $i ] = curl_init();
      curl_setopt( $ch [ $i ], CURLOPT_URL, $task );
      curl_setopt( $ch [ $i ], CURLOPT_RETURNTRANSFER, 1);
      curl_multi_add_handle( $mh , $ch [ $i ]);
   }
   do { $mrc = curl_multi_exec( $mh , $active ); } while ( $mrc == CURLM_CALL_MULTI_PERFORM);
   while ( $active && $mrc == CURLM_OK) {
      if (curl_multi_select( $mh ) != -1) {
       do { $mrc = curl_multi_exec( $mh , $active ); } while ( $mrc == CURLM_CALL_MULTI_PERFORM);
      }
   }
   // completed, checkout result
   foreach ( $tasks as $j => $task ){
      if (curl_error( $ch [ $j ])){
        echo "task ${j} [$task ] error " , curl_error( $ch [ $j ]), "\r\n" ;
      } else {
        echo "task ${j} [$task ] get: \r\n" , curl_multi_getcontent( $ch [ $j ]), "\r\n" ;
      }
   }
?>

编写脚本 test2.php
 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
   date_default_timezone_set( 'Asia/Chongqing' );
   echo "child start, pid " , getmypid (), "\r\n" ;
   for ( $i =0; $i <5; ++ $i ){
      beep();
   }
   exit (0);
   // ***
   function beep(){
     echo getmypid (), "\t" , date ( 'Y-m-d H:i:s' , time()), "\r\n" ;
     sleep(1);
   }
?>

用命令行运行

?
1
#php -f test1.php &

输出结果

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
child start, pid 5804
5804  2013-01-15 20:22:35
5804  2013-01-15 20:22:36
5804  2013-01-15 20:22:37
5804  2013-01-15 20:22:38
5804  2013-01-15 20:22:39
 
child start, pid 5804
5804  2013-01-15 20:22:35
5804  2013-01-15 20:22:36
5804  2013-01-15 20:22:37
5804  2013-01-15 20:22:38
5804  2013-01-15 20:22:39
 
child start, pid 5804
5804  2013-01-15 20:22:35
5804  2013-01-15 20:22:36
5804  2013-01-15 20:22:37
5804  2013-01-15 20:22:38
5804  2013-01-15 20:22:39

从打印的时间看到,多个任务几乎是同时运行的。 


3.关于几个学习过程中不懂的坑:

1.for循环创建子进程的时候一定记住子进程要exit,否则,子进程也会参与到循环创建子进程的过程中,就乱了。

2.不要在子进程中使用父进程的实例,之前百度金融雷达踩坑。

3.孤儿进程,父进程退出,子进程还在跑。这个没事,一般不用处理

4.僵尸进程,子进程退出,父进程还在跑,但是没回收子进程。因此,父进程里一定要有回收子进程的逻辑。如非阻塞方式里面的garbage函数进行了处理,实际上garbage就干了一件事最关键就是:pcntl_waitpid函数,只有执行这个函数,这个子进程才最终释放。


5.pcntl_waitpid函数,挂起来父进程知道要等待的那个子进程运行完毕。WNOHANG的作用是如果没子进程退出就立刻返回0,不等待,加了这个参数后,这个函数所在的父进程不会阻塞,即不会一直等到所有子进程结束后才结束。如果有等待所有子进程结束后父进程才结束,可以在父进程中记录下所有fork出去的子进程,然后循环执行这个函数pcntl_waitpid(-1,$status),没返回一个子进程就代表这个子进程结束了,记录下这个子进程,直到和父进程之前记录下的所有fork出去的进程数量一致了再结束父进程。

6.关于kill父进程的时候同时kill掉子进程的方法。需要在子进程里加一个信号器。
7.关于信号注册器和信号执行器:http://www.jb51.net/article/56301.htm
特别注意区分declare函数和pcntl_signal_dispatch,你给程序发的信号,程序会把这个信号存起来,第二个这个是执行到指定地方会检查有没有信号被存进来,第一个是每执行一条机器指令就检查一下。相关文章:http://blog.csdn.net/udefined/article/details/24333333






$test = array(1,2,3
<?php
$test = array(1,2,3);
//declare(ticks=1);
foreach($test as $v) {
    pcntl_signal(SIGCHLD,'a');
    pcntl_signal(SIGINT,'signal_handler');//父子进程都会被注册这个信号处理器
    pcntl_signal_dispatch();// 如果你在这段语句执行过以后你传进来信号,则信号不会被处理
    $pid = pcntl_fork();
    if($pid == -1) {die('could not fork');} else {
        if($pid) {
            sleep(10);
            pcntl_wait($status);var_dump($status);
            var_dump('i am dad' . $pid);
        } else {
            var_dump('i am kid' . $pid);
            sleep(10);//如果信号在这个函数执行的过程中被输入,这个过程会被打断。直接执行下一个函数'i am kid end',并会执行dispatch之前的所有函数。即接收信号会打断接收时正在执行的函数。
            var_dump('i am kid end');exit;
        }   
    }var_dump('-------',$pid);
//  pcntl_signal_dispatch();// 如果你放在这里的话,在这个函数执行之前输入的信号,信号会被执行,但是前面的代码都会被执行就算你在
'i am kid'这句话之前输入了中断信号,程序也不会中断,还是会打印出来这句'i am kid'.
}function a() {echo 'handle child';}function signal_handler() {


);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值