几种方式在php中实现定时器

使用场景

在用php写逻辑代码的时候一般很少碰到需要严格定时器的场景,因为大家总是希望在一次同步的请求中完成所有的任务,如果不是逼不得已,可能也很少将业务拆分到异步的任务去完成。我认为主要还是自己搭建一套靠谱的、通用的异步任务执行框架比较困难,也比较麻烦。但是现在有很多创意十足的php扩展或者是PaaS平台已经有好的实现了。利用这些工具或者框架构建一套自己的异步任务执行框架就简单的多了。这时候定时器就派上了用场,例如在我们拆分子任务执行的时候将其执行的时间设置为一个固定的值,在任务调度时时如果在设定的时间后没有完成的重新触发,这样就可以保证其反复执行直到成功。下面会提到几种方式分别限制一段php代码的执行时间,各有利弊。

使用max_execution_time

这个可能是大家最熟悉的方式了,通过php.ini能配置的一个参数来限制脚本的最大执行时间。给出测试的代码:

<?php
$i = 1;
while (1) {
    $i++;
    usleep(1);
}

这时候通过命令行执行我们的代码,并通过-d参数限制最大的执行时间就可以看到:

[root@localhost /]# php -d "max_execution_time=1" test.php
PHP Fatal error:  Maximum execution time of 1 second exceeded in /data1/www/htdocs/sae.changes.com.cn/test.php on line 5

很显然是起到了限制的作用,这时候我们稍微修改下测试的代码。看看在这种强执行时间限制之下能不能执行我们设置的register_shutdown_function,测试代码如下:

<?php
function shutdown()
{
    echo 'success exec';
}
register_shutdown_function('shutdown');

$i = 1;
while (1) {
    $i++;
    usleep(1);
}

执行测试代码可以看到:

[root@localhost /]# php -d "max_execution_time=1" test.php
PHP Fatal error:  Maximum execution time of 1 second exceeded in /data1/www/htdocs/sae.changes.com.cn/test.php on line 11
success exec

太好了,说明这种方式做执行时间的限制还不赖。但是还是有些不方便的地方,这主要在于会同安全组的策略相违背,至少我们在php安全的设置的初期是把ini_set函数屏蔽了的。这时候使用这种方式可能会在部分云平台上会出现问题。

使用php中的declare ticks指令完成

这个东西可以简单的理解为php为开发者为基本的执行单元执行后加了可以hook的函数,具体的介绍可以参考php的文档http://php.net/manual/zh/control-structures.declare.php,先看例子和执行结果:

<?php
declare(ticks = 1);
function shutdown()
{
    echo "safe shutdown \n";
}
register_shutdown_function('shutdown');
define('max_exectime', 1);
$time_start = microtime(true);
var_dump($time_start);
function check_exec_time()
{
    global $time_start;
    $time_now = microtime(true);
    if ($time_now - max_exectime > $time_start) {
        var_dump($time_now);
        echo "bye";
        exit();
    }

}
register_tick_function('check_exec_time');

for ($i = 0; $i < 10000100; $i++) {
    $i++;
    usleep(1);
}

我们可以看到执行的结果:

[root@localhost /]# php tick.php
float(1442126693.6926)
float(1442126694.6926)
byesafe shutdown

可以看到借助使用ticks指令实现定时器比max_execution_time的好处在于前者可以轻松实现毫秒级别的定时器但是max_execution_time最小的粒度只能到秒级别。另外这种方式不会和大部分的安全策略相违背。但是缺点也是很明显了,指定的周期设置的越短定时越精确,但是对性能的损耗也越大,因为总要跳出来去检测时间。

swoole的定时器

使用过swoole扩展的同学肯定知道这个扩展也提供了毫秒定时器的功能,具体可以参考http://wiki.swoole.com/wiki/page/174.html。当然在使用时必须要编译安装swoole扩展,给出测试代码:

<?php
var_dump('swoole version:'.swoole_version()."\n");
swoole_timer_tick(1000, 'exec_function');
swoole_timer_after(1, function(){
     echo(1);
});

function exec_function()
{
    // 这里直接退出了
    echo "timer exec\n";
    var_dump(getmypid());
    exit();
}

这时我们执行可以得到结果:

[root@localhost /]# php swoole.php
string(22) "swoole version:1.7.19"
1timer exec
int(19908)

可以看到1000毫秒的定时器工作了,swoole_timer_after的回调函数中用于执行需要执行的代码逻辑,这种用法有许多的依赖,首先swoole在实现的时候使用了timerfd,但是这个东西在内核版本2.6.25后才加入,如果内核版本低于这个应该是无法工作的,另外swoole的定时器需要在“全异步”的环境中才能工作,暂时不能用于apache等环境中,不能使用usleep、sleep函数阻塞进程,否则就无法工作了。