PHP定时执行任务实现方法详解(Timer)


Posted in PHP onJuly 30, 2015

下面通过理论讲解,实例分析,效果展示的方式给大家分享下PHP定时执行任务实现方法。

定时器任务,在WEB应用比较常见,如何使用PHP实现定时器任务,大致有两种方案:

1)使用Crontab命令,写一个shell脚本,在脚本中调用PHP文件,然后定期执行该脚本;

2)配合使用ignore_user_abort()和set_time_limit(),使脚本脱离浏览器运行。

前者是利用Linux的特性,和PHP本身没有多大关系,后者使用场景有限,且只能由一次HTTP请求触发该脚本,执行完后退出。那么我们如何使用纯PHP实现纯粹的定时器任务,且能适应认识任务业务需求?

基础知识

此程序在Linux下开发,以cli模式运行,一下是基本知识的简要介绍。

CLI:PHP的命令行模式,常见的WEB应用使用的是fpm;
进程:进程是程序运行的基本单元,进程之间是独立运行且互不干扰的,有独立的运行空间,每个进程都有一个进程控制块;
进程间通信:既然进程是独立运行,我们需要一种机制保证不同进程信息的交换,进程间通信主要包括:管道,IPC(共享内存,信号,消息队列),套接字;
PCNTL扩展:PHP的一个进程扩展,主要用到pcntl_alarm()函数,详细介绍请查阅官网.
实现原理

用一个三维数组保存所有需要执行的任务,一级索引为时间戳,值为执行任务的方法、回调参数等,具体数组形式如下:

array(

        '1438156396' => array(

                array(1,array('Class','Func'), array(), true),  

        )

)

说明:
1438156396 时间戳

array(1,array('Class','Func'), array(), true)

参数依次表示: 执行时间间隔,回调函数,传递给回调函数的参数,是否持久化(ture则一直保存在数据中,否则执行一次后删除)

这些任务可以是任意类的方法。既然是定时任务,我们需要一个类似计时的东东,此方案采用信号量去做,每一秒向当前进程发送SIGALRM信号,并捕获该信号,触发信号处理函数,循环遍历数据,判断是否有当前时间需要执行的任务。如果有则采用回调方式触发,并把参数传递给该方法。

<?php
 /**
 *定时器
 */
 class Timer
 {
  //保存所有定时任务
  public static $task = array();
 
   //定时间隔
   public static $time = 1;
 
   /**
  *开启服务
   *@param $time int
  */
  public static function run($time = null)
   {
   if($time)
    {
      self::$time = $time;
    }
    self::installHandler();
    pcntl_alarm(1);
   }
   /**
   *注册信号处理函数
   */
   public static function installHandler()
   {
    pcntl_signal(SIGALRM, array('Timer','signalHandler'));
   }
 
   /**
   *信号处理函数
   */
   public static function signalHandler()
   {
    self::task();
   //一次信号事件执行完成后,再触发下一次
   pcntl_alarm(self::$time);
   }
 
   /**
   *执行回调
   */
   public static function task()
   {
    if(empty(self::$task))
    {//没有任务,返回
      return ;
    }
    foreach(self::$task as $time => $arr)
   {
      $current = time();
   
     foreach($arr as $k => $job)
    {//遍历每一个任务
       $func = $job['func']; /*回调函数*/
       $argv = $job['argv']; /*回调函数参数*/
     $interval = $job['interval']; /*时间间隔*/
       $persist = $job['persist']; /*持久化*/
 
       if($current == $time)
       {//当前时间有执行任务
 
      //调用回调函数,并传递参数
        call_user_func_array($func, $argv);
      
      //删除该任务
        unset(self::$task[$time][$k]);
       }
       if($persist)
       {//如果做持久化,则写入数组,等待下次唤醒
        self::$task[$current+$interval][] = $job;
       }
    }
    if(empty(self::$task[$time]))
    {
     unset(self::$task[$time]);
    }
    }
   }
 
   /**
   *添加任务
   */
   public static function add($interval, $func, $argv = array(), $persist = false)
   {
    if(is_null($interval))
    {
     return;
    }
    $time = time()+$interval;
   //写入定时任务
   self::$task[$time][] = array('func'=>$func, 'argv'=>$argv, 'interval'=>$interval, 'persist'=>$persist);
   }
 
   /**
   *删除所有定时器任务
   */
   public function dellAll()
   {
    self::$task = array();
   }
 }

 

这是定时器类核心部分,有一个静态变量保存有所有需要执行的任务,这里为什么是静态的呢?大家自行思考.当进程接受到 SIGALRM 信号后,触发 signalHandler 函数,随后循序遍历数组查看是否有当前时间需要执行的任务,有则回调,并传递参数,删除当前job,随后检查是否要做持久化任务,是则继续将当前job写入事件数组等待下次触发,最后再为当前进程设置一个闹钟信号.可以看出这个定时器,只要触发一次就会从内部再次触发,得到自循环目的.

 

<?php
 
 class DoJob
 {
  public function job( $param = array() )
  {
   $time = time();
   echo "Time: {$time}, Func: ".get_class()."::".__FUNCTION__."(".json_encode($param).")\n";
  }
 }

这是回调类及函数,为方便说明,加入不少调试信息.Timer类及回调都有了,我们看看使用场景是怎么样的.
 

<?php
 
 require_once(__DIR__."/Timer.php");
 require_once(__DIR__."/DoJob.php");
 
 
 Timer::dellAll();
 
 Timer::add( 1, array('DoJob','job'), array(),true);
 
 Timer::add( 3, array('DoJob','job'),array('a'=>1), false);
 
 echo "Time start: ".time()."\n";
 Timer::run();
 
 while(1)
 {
  sleep(1);
  pcntl_signal_dispatch();
 }

代码非常短,这里注册了两个job,随后运行定时器,在一个无限循环里捕捉信号触发动作,如果不捕获将无法触发事先注册的处理函数.这样一个自循环的定时器开发完成.运行结果如下:

PHP定时执行任务实现方法详解(Timer)

如我们场景类添加的任务一样,在90的时候执行了两个任务,一个为持久化的不带参数的job,一个为非持久化带参数的job,随后非持久化job不再执行.

总结

在收到信号前,当前进程不能退出.这里我使用了条件永远为真的循环.在我们实际生产环境中,需要创造这么一个先决条件,比如说,我们有一组服务,这些服务都是一直运行的,不管是IO访问,等待socket链接等等,当前服务都不会终止,即使进程阻塞也不会有问题,这种场景,也就是有一个一直运行的服务中使用.
目前PHP只支持以秒为单位的触发,不支持更小时间单位,对位定时任务而言基本足够

以上内容就是本文的全部介绍,希望大家喜欢。

PHP 相关文章推荐
php获取mysql版本的几种方法小结
Mar 25 PHP
php处理restful请求的路由类分享
Feb 27 PHP
PHP中copy on write写时复制机制介绍
May 13 PHP
php中current、next与reset函数用法实例
Nov 17 PHP
PHP中SQL查询语句的id=%d解释(推荐)
Dec 10 PHP
Yii2下session跨域名共存的解决方案
Feb 04 PHP
利用php生成验证码
Feb 23 PHP
windows下的WAMP环境搭建图文教程(推荐)
Jul 27 PHP
ThinkPHP5.1表单令牌Token失效问题的解决
Mar 22 PHP
Laravel自定义 封装便捷返回Json数据格式的引用方法
Sep 29 PHP
浅析PHP中的 inet_pton 网络函数
Dec 16 PHP
gearman管理工具GearmanManager的安装与php使用方法示例
Feb 27 PHP
php实现curl模拟ftp上传的方法
Jul 29 #PHP
php实现复制移动文件的方法
Jul 29 #PHP
详解PHP中的Traits
Jul 29 #PHP
php实现在多维数组中查找特定value的方法
Jul 29 #PHP
1亿条数据如何分表100张到Mysql数据库中(PHP)
Jul 29 #PHP
php实现仿写CodeIgniter的购物车类
Jul 29 #PHP
PHP使用逆波兰式计算工资的方法
Jul 29 #PHP
You might like
PHP生成静态页面详解
2006/12/05 PHP
PHP中file_get_contents高?用法实例
2014/09/24 PHP
php冒泡排序与快速排序实例详解
2015/12/07 PHP
Zend Framework教程之Zend_Db_Table表关联实例详解
2016/03/23 PHP
Laravel框架下的Contracts契约详解
2020/03/17 PHP
Extjs中TabPane如何嵌套在其他网页中实现思路及代码
2013/01/27 Javascript
jquery入门——事件机制之事件中的冒泡现象示例解释
2020/09/12 Javascript
高效的获取当前元素是父元素的第几个子元素
2013/10/15 Javascript
JavaScript设置首页和收藏页面的小例子
2013/11/11 Javascript
用nodejs实现PHP的print_r函数代码
2014/03/14 NodeJs
jQuery.extend()、jQuery.fn.extend()扩展方法示例详解
2014/05/08 Javascript
JavaScript支持的最大递归调用次数分析
2014/06/24 Javascript
jQuery实现点击图片翻页展示效果的方法
2015/02/16 Javascript
Vue键盘事件用法总结
2017/04/18 Javascript
JS抛物线动画实例制作
2018/02/24 Javascript
使vue实现jQuery调用的两种方法
2019/05/12 jQuery
ElementUI多个子组件表单的校验管理实现
2019/11/07 Javascript
JS canvas实现画板和签字板功能
2021/02/23 Javascript
python实现超简单端口转发的方法
2015/03/13 Python
Python使用CMD模块更优雅的运行脚本
2015/05/11 Python
基于python脚本实现软件的注册功能(机器码+注册码机制)
2016/10/09 Python
linux环境下python中MySQLdb模块的安装方法
2017/06/16 Python
Python在for循环中更改list值的方法【推荐】
2018/08/17 Python
python 协程 gevent原理与用法分析
2019/11/22 Python
解决pycharm每次打开项目都需要配置解释器和安装库问题
2020/02/26 Python
HTML5实现经典坦克大战坦克乱走还能发出一个子弹
2013/09/02 HTML / CSS
澳大利亚女性快速时尚零售商:Ally Fashion
2018/04/25 全球购物
电气工程及其自动化自我评价四篇
2013/09/24 职场文书
大学生职业生涯规划书模板
2014/01/03 职场文书
优秀教师获奖感言
2014/01/31 职场文书
班训口号大全
2014/06/18 职场文书
幼儿教师2014年度工作总结
2014/12/16 职场文书
护士实习自荐信
2015/03/06 职场文书
《围炉夜话》110句人生箴言,精辟有内涵,引人深思
2019/10/23 职场文书
Java基础之详解HashSet的使用方法
2021/06/30 Java/Android
Python使用pandas导入csv文件内容的示例代码
2022/12/24 Python