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

下面通过理论讲解,实例分析,效果展示的方式给大家分享下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,随后运行定时器,在一个无限循环里捕捉信号触发动作,如果不捕获将无法触发事先注册的处理函数.这样一个自循环的定时器开发完成.运行结果如下:

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

总结

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

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

(0)

相关推荐

  • 利用php-cli和任务计划实现刷新token功能的方法

    1.业务需求 需要实现这样一个功能:在第三方授权的认证当中,在用户首次登录授权我们会得到一个access_token,有效期为25小时,还会得到一个refresh_token,有效期为30天. 我们只要保存好这个refresh_token,在30天内我们都可以用这个refresh_token去请求一个api,他会返回一个新的access_token.这样我们只需要让用户授权一次,我们就可以获得长达30天的一个授权期限. 这里可以分为几个点: <1>这个应该是要定期执行的一个任务. 25小时才会

  • php中定时计划任务的实现原理

    根据php手册简单介绍一些相关的知识: 1.连接处理: 在 PHP 内部,系统维护着连接状态,其状态有三种可能的情况: 0 - NORMAL(正常) 1 - ABORTED(异常退出) 2 - TIMEOUT(超时) 当 PHP 脚本正常地运行 NORMAL 状态时,连接为有效.当远程客户端中断连接时,ABORTED 状态的标记将会被打开.远程客户端连接的中断通常是由用户点击 STOP 按钮导致的.当连接时间超过 PHP 的时限时,TIMEOUT 状态的标记将被打开. 可以决定脚本是否需要在客户

  • PHP计划任务、定时执行任务的实现代码

    不知道能程序的性能会不会影响很大! 复制代码 代码如下: ignore_user_abort();//关掉浏览器,PHP脚本也可以继续执行. set_time_limit(0);// 通过set_time_limit(0)可以让程序无限制的执行下去 $interval=60*30;// 每隔半小时运行 do{ //这里是你要执行的代码 sleep($interval);// 等待5分钟 }while(true);

  • php定时计划任务的实现方法详解

    我在uchome 中 分析到, uchome是这样做的1. 把所有的计划任务存放到数据库2. 每次用户进行操作或打开页面的时候都按排序执行一条计划任务. 也就是轮番判断,时间到了的, 就在用户进程中执行.uchome的计划任务代码在 ./source/function_cron.php上面只自己分析uchome代码关于计划任务大概的结果, 抛砖引玉. 希望谁有好的方法拿出来共享.ignore_user_abort()我前一段时间彻底研究过计划任务,认为计划任务的思路很多,但最适合Web的还是触发

  • PHP定时执行计划任务的多种方法小结

    PHP定时执行的三种方式实现 1.windows 的计划任务 2.linux的脚本程序 3.让web浏览器定时刷新 具体实现 windows计划任务 PHP很少在win服务器上跑,具体实现也不再深究,看网上实现的原理大概是写bat脚本,然后让window任务添加执行这个bat脚本,具体可以参考:http://www.jb51.net/article/29134.htm linux 的脚本实现 这里主要使用到crontab这个命令, 使用方式 : crontab filecrontab [ -u

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

    下面通过理论讲解,实例分析,效果展示的方式给大家分享下PHP定时执行任务实现方法. 定时器任务,在WEB应用比较常见,如何使用PHP实现定时器任务,大致有两种方案: 1)使用Crontab命令,写一个shell脚本,在脚本中调用PHP文件,然后定期执行该脚本: 2)配合使用ignore_user_abort()和set_time_limit(),使脚本脱离浏览器运行. 前者是利用Linux的特性,和PHP本身没有多大关系,后者使用场景有限,且只能由一次HTTP请求触发该脚本,执行完后退出.那么我

  • Shell脚本中多命令逻辑执行顺序的方法详解

    Linux中可以使用分号";".双and号"&&"和双竖线"||"来连接多个命令.单"&"符号也算命令连接符号,只不过它是将其前面的命令放入后台执行,所以可以变相地实现命令并行执行. 1.分号";" command1 ; command2 命令之间没有逻辑关系.分号连接的命令会按照顺序从前向后依次执行,但分号两端的命令之间没有任何逻辑关系,所有写出来的命令最终都会被执行,即使分号前面

  • PHP定时执行任务的3种方法详解

    PHP定时执行的三种方式实现 1.windows 的计划任务 2.linux的脚本程序 3.让web浏览器定时刷新 具体实现 1.windows计划任务 PHP很少在win服务器上跑,具体实现也不再深究,看网上实现的原理大概是写bat脚本,然后让window任务添加执行这个bat脚本. 2.linux 的脚本实现 这里主要使用到crontab这个命令, 使用方式 : 复制代码 代码如下: crontab   filecrontab [ -u user ] [ -u user ] { -l | -

  • PHP实现执行外部程序的方法详解

    本文实例讲述了PHP实现执行外部程序的方法.分享给大家供大家参考,具体如下: 在一些特殊情况下,会使用PHP调用外部程序执行,比如:调用shell命令.shell脚本.可执行程序等等,今天在源码中了解了一下PHP执行外部程序的方法,借此机会顺便整理一下. 在源码中 exec.h文件中,列出了可调用外部程序的几个函数,蓝色框里的两个函数只是辅助作用,本文只对其他几个函数做介绍. 前提 1 PHP没有运行在安全模式,关掉安全模式,即:safe_mode = off 2 禁用函数列表 disable_

  • iOS当多个网络请求完成后执行下一步的方法详解

    前言 在开发中,我们很容易遇到这样的需求,需要我们同时做多个网络请求,所有网络请求都完成后才能进行下一步的操作. 网络请求的任务是提交给子线程异步处理了,网络请求这样的任务也就快速执行完毕了,但是网络请求是一个任务,处理收到的网络响应又是一个任务,注意不要把这两个过程混为一谈. 如下载多个图片,下载完了才能展示,今天我们就来研究一下这个问题的解决方案. 解决方法 1.首先,我们创建一个项目,然后做一般性的做法,不做任何处理去连续请求一个接口10次: 先在viewDidLoad中创建第一种情况.

  • java执行SQL语句实现查询的通用方法详解

    完成SQL查询 并将查询结果放入Vector容器,以便其他程序使用 /* * 执行sql查询语句 */ public static <T> Vector<T> executeQuery(Class<T> clazz, String sql, Object... args) { Connection conn = null; PreparedStatement preparedstatement = null; ResultSet rs = null; Vector<

  • 对python_discover方法遍历所有执行的用例详解

    当我们写了一个单个py的测试文件时直接运行就ok了,但当我们有很多很多个这样的py时,难道要一个一个的点击来运行吗,当然不是.我们可以通过discover方法来找到所有的用例. 下面直接举例说明discover用法: 一. 准备工作 目录结构: DiscoverCase.py 文件代码: import unittest import os def discover_case(case_dir): # 待执行用例的目录 testcase = unittest.TestSuite() discove

  • java 使用idea将工程打成jar并创建成exe文件类型执行的方法详解

    第一部分: 使用idea 打包工程jar 1.准备好一份 开发好的 可执行的 含有main方法的 工程. 例如:我随便写的main方法 public static void main(String[] args) throws IOException { Properties properties = System.getProperties(); String osName = properties.getProperty("os.name"); System.out.println

  • Java JDBC批量执行executeBatch方法详解

    JDBC事务 在数据库中,所谓事务是指一组逻辑操作单元,使数据从一种状态变换到另一种状态.为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态. 事务的操作:先定义开始一个事务,然后对数据作修改操作,这时如果提交(COMMIT),这些修改就永久地保存下来,如果回退(ROLLBACK),数据库管理系统将放弃您所作的所有修改而回到开始事务时的状态. 事

  • Python执行dos和Linux命令的方法详解

    在实际开发中,有时为了方便,可能需要执行dos命令或者Linux命令.比如说执行某些shell脚本,上传下载一些文件,执行adb命令等跨语言,加压包,解压包等跨操作系统的场景.这样能大大加强多个平台和操作系统之间的关联性. windows: 案例1:弹窗式执行dos命令(与打开cmd执行一模一样) # -*- coding: utf-8 -*- import os # 解决打印时,部分中文乱码问题 os.popen('chcp 65001') # 查看当前路径 command1 = "chdir

随机推荐