深入探究PHP的多进程编程方法
author:一佰互联 2019-04-27   click:165

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

<?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循环就可以了。   

 #如果获得的总数小于或等于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 

<?php  // example of multiple processes  date_default_timezone_set( "Asia/Chongqing");  echo "parent start, pid ", getmypid(), "" ;  beep();  for ($i=0; $i<3; ++$i){     $pid = pcntl_fork();      if ($pid == -1){         die ("cannot fork" );     } else if ($pid > 0){         echo "parent continue ";         for ($k=0; $k<2; ++$k){           beep();        }     } else if ($pid == 0){         echo "child start, pid ", getmypid(), "" ;         for ($j=0; $j<5; ++$j){           beep();        }         exit ;     }  }  // ***  function beep(){      echo getmypid(), "	" , date( "Y-m-d H:i:s", time()), "" ;     sleep(1);  }?>

用命令行运行

#php -f test.php

输出结果

parent start, pid 17931793  2013-01-14 15:04:17parent continue1793  2013-01-14 15:04:18child start, pid 17941794  2013-01-14 15:04:181794  2013-01-14 15:04:191793  2013-01-14 15:04:191794  2013-01-14 15:04:20parent continue1793  2013-01-14 15:04:20child start, pid 17951795  2013-01-14 15:04:2017931794        2013-01-14 15:04:212013-01-14 15:04:211795  2013-01-14 15:04:211794  2013-01-14 15:04:221795  2013-01-14 15:04:22parent continue1793  2013-01-14 15:04:22child start, pid 17961796  2013-01-14 15:04:221793  2013-01-14 15:04:231796  2013-01-14 15:04:231795  2013-01-14 15:04:231795  2013-01-14 15:04:241796  2013-01-14 15:04:241796  2013-01-14 15:04:251796  2013-01-14 15:04:26

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

2. 阻塞方式

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

$pid = pcntl_fork();if ($pid == -1){  ...} else if ($pid > 0){   echo "parent continue ";   pcntl_wait($status);   for ($k=0; $k<2; ++$k){     beep();  }} else if ($pid == 0){   ...}

用命令行运行

#php -f test.php

输出结果

parent start, pid 18071807  2013-01-14 15:20:05parent continuechild start, pid 18081808  2013-01-14 15:20:061808  2013-01-14 15:20:071808  2013-01-14 15:20:081808  2013-01-14 15:20:091808  2013-01-14 15:20:101807  2013-01-14 15:20:111807  2013-01-14 15:20:12parent continuechild start, pid 18091809  2013-01-14 15:20:131809  2013-01-14 15:20:141809  2013-01-14 15:20:151809  2013-01-14 15:20:161809  2013-01-14 15:20:171807  2013-01-14 15:20:181807  2013-01-14 15:20:19child start, pid 18101810  2013-01-14 15:20:20parent continue1810  2013-01-14 15:20:211810  2013-01-14 15:20:221810  2013-01-14 15:20:231810  2013-01-14 15:20:241807  2013-01-14 15:20:251807  2013-01-14 15:20:26

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

3. 非阻塞方式

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

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

用命令行运行

#php -f test.php &

输出结果

parent start, pid 20662066  2013-01-14 16:45:34parent continue2066  2013-01-14 16:45:35child start, pid 20672067  2013-01-14 16:45:3520662067        2013-01-14 16:45:362013-01-14 16:45:362067  2013-01-14 16:45:37parent continue2066  2013-01-14 16:45:37child start, pid 20682068  2013-01-14 16:45:372067  2013-01-14 16:45:382068  2013-01-14 16:45:382066  2013-01-14 16:45:38parent continue2066  2013-01-14 16:45:40child start, pid 20692069  2067  2013-01-14 16:45:402013-01-14 16:45:402068  2013-01-14 16:45:402066  2013-01-14 16:45:412069  2013-01-14 16:45:412068  2013-01-14 16:45:41signel 17 received     child end pid 2067, status 02069  2013-01-14 16:45:422068  2013-01-14 16:45:422069  2013-01-14 16:45:43signel 17 received     child end pid 2068, status 02069  2013-01-14 16:45:44signel 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是父进程,子进程被回收了。

子进程退出状态

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

 返回子进程的结束状态

windows下多线程

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

编写脚本 test1.php 

<?php  date_default_timezone_set( "Asia/Chongqing");  $tasks = array(     "http://localhost/feedbowl/t2.php?job=task1",     "http://localhost/feedbowl/t2.php?job=task2",     "http://localhost/feedbowl/t2.php?job=task3"  );  $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]), "" ;     } else {       echo "task ${j} [$task ] get: " , curl_multi_getcontent($ch[$j]), "" ;     }  }?>

编写脚本 test2.php 

<?php  date_default_timezone_set( "Asia/Chongqing");  echo "child start, pid ", getmypid(), "" ;  for ($i=0; $i<5; ++$i){     beep();  }  exit (0);  // ***  function beep(){    echo getmypid(), "	" , date("Y-m-d H:i:s" , time()), "";    sleep(1);  }?>

用命令行运行

#php -f test1.php &

输出结果

task 0 [http://localhost/feedbowl/t2.php?job=task1] get:child start, pid 58045804  2013-01-15 20:22:355804  2013-01-15 20:22:365804  2013-01-15 20:22:375804  2013-01-15 20:22:385804  2013-01-15 20:22:39task 1 [http://localhost/feedbowl/t2.php?job=task2] get:child start, pid 58045804  2013-01-15 20:22:355804  2013-01-15 20:22:365804  2013-01-15 20:22:375804  2013-01-15 20:22:385804  2013-01-15 20:22:39task 2 [http://localhost/feedbowl/t2.php?job=task3] get:child start, pid 58045804  2013-01-15 20:22:355804  2013-01-15 20:22:365804  2013-01-15 20:22:375804  2013-01-15 20:22:385804  2013-01-15 20:22:39

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