深入探究PHP的多进程编程方法


Posted in PHP onAugust 18, 2015

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

<?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(), "\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);
  }
?>

用命令行运行

#php -f test.php

输出结果

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()方法等待进程结束,然后回收已经结束的进程。
将测试脚本改成:
 

$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){
   ...
}

用命令行运行

#php -f test.php

输出结果

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. 非阻塞方式

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

<?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);
  }
?>

用命令行运行

#php -f test.php &

输出结果

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
是父进程,子进程被回收了。

子进程退出状态

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]), "\r\n" ;
     } else {
       echo "task ${j} [$task ] get: \r\n" , curl_multi_getcontent($ch[$j]), "\r\n" ;
     }
  }
?>

编写脚本 test2.php
 

<?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);
  }
?>

用命令行运行

#php -f test1.php &

输出结果

task 0 [http://localhost/feedbowl/t2.php?job=task1] get:
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

task 1 [http://localhost/feedbowl/t2.php?job=task2] get:
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

task 2 [http://localhost/feedbowl/t2.php?job=task3] get:
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

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

PHP 相关文章推荐
用PHP和ACCESS写聊天室(三)
Oct 09 PHP
关于php连接mssql:pdo odbc sql server
Jul 20 PHP
php下利用curl判断远程文件是否存在的实现代码
Oct 08 PHP
解析:通过php socket并借助telnet实现简单的聊天程序
Jun 18 PHP
访问编码后的中文URL返回404错误的解决方法
Aug 20 PHP
php常见的魔术方法详解
Dec 25 PHP
PHP获取指定月份第一天和最后一天的方法
Jul 18 PHP
PHP代码判断设备是手机还是平板电脑(两种方法)
Oct 19 PHP
PHP数组去重的更快实现方式分析
May 09 PHP
解决laravel 出现ajax请求419(unknown status)的问题
Sep 03 PHP
Thinkphp5.0 框架Model模型简单用法分析
Oct 11 PHP
PHP策略模式写法
Apr 01 PHP
使用PHP和JavaScript判断请求是否来自微信内浏览器
Aug 18 #PHP
PHP的命令行命令使用指南
Aug 18 #PHP
使用PHP生成图片的缩略图的方法
Aug 18 #PHP
Nginx服务器上安装并配置PHPMyAdmin的教程
Aug 18 #PHP
详解PHP中的mb_detect_encoding函数使用方法
Aug 18 #PHP
深入讲解PHP Session及如何保持其不过期的方法
Aug 18 #PHP
php微信公众平台开发之获取用户基本信息
Aug 17 #PHP
You might like
php递归列出所有文件和目录的代码
2008/09/10 PHP
如何使用PHP Embed SAPI实现Opcodes查看器
2015/11/10 PHP
PHP实现转盘抽奖算法分享
2020/04/15 PHP
用javascript实现点击链接弹出&quot;图片另存为&quot;而不是直接打开
2007/08/15 Javascript
JS定时器实例
2013/04/17 Javascript
jquery Ajax 实现加载数据前动画效果的示例代码
2014/02/07 Javascript
浅谈nodeName,nodeValue,nodeType,typeof 的区别
2015/01/13 Javascript
javascript实现 百度翻译 可折叠的分享按钮列表
2015/03/12 Javascript
JavaScript实现简单图片翻转的方法
2015/04/17 Javascript
JS模拟酷狗音乐播放器收缩折叠关闭效果代码
2015/10/29 Javascript
JavaScript+canvas实现七色板效果实例
2016/02/18 Javascript
简述JavaScript提交表单的方式 (Using JavaScript Submit Form)
2016/03/18 Javascript
JS中利用swiper实现3d翻转幻灯片实例代码
2017/08/25 Javascript
Angular6新特性之Angular Material
2018/12/28 Javascript
jQuery zTree插件快速实现目录树
2019/08/16 jQuery
使用 JavaScript 创建并下载文件(模拟点击)
2019/10/25 Javascript
JavaScript实现打砖块游戏
2020/02/25 Javascript
用vue设计一个日历表
2020/12/03 Vue.js
在Python中使用第三方模块的教程
2015/04/27 Python
python获取时间及时间格式转换问题实例代码详解
2018/12/06 Python
Python3.x+迅雷x 自动下载高分电影的实现方法
2020/01/12 Python
CSS3 简单又实用的5个属性
2010/03/04 HTML / CSS
利用CSS3的transform做的动态时钟效果
2011/09/21 HTML / CSS
在IE6系列等老式浏览器中使用HTML5的新标签实现方案
2012/12/25 HTML / CSS
英国计算机产品零售商:Novatech(定制个人电脑、笔记本电脑、工作站和服务器)
2018/01/28 全球购物
优衣库美国官网:UNIQLO美国
2018/04/14 全球购物
人力资源主管的岗位职责
2014/03/15 职场文书
环境科学专业求职信
2014/08/04 职场文书
2014银行领导班子四风对照检查材料思想汇报
2014/09/25 职场文书
冲出亚马逊观后感
2015/06/03 职场文书
KTV员工管理制度
2015/08/06 职场文书
幼儿教师远程研修感悟
2015/11/18 职场文书
2016年小学教师师德承诺书
2016/03/25 职场文书
2017年寒假社区服务活动总结
2016/04/06 职场文书
Nginx快速入门教程
2021/03/31 Servers
CSS作用域(样式分割)的使用汇总
2021/11/07 HTML / CSS