天天看点

【Swoole系列5.1】毫秒定时器

毫秒定时器

PHP 中有没有定时器?还记得我们之前讲过这个东西吧。如果不记得的小伙伴,可以移步之前的文章中再去重温一下 PHP没有定时器?PHP没有定时器 https://mp.weixin.qq.com/s/NIYwhVLRl0drIcRvIoWvJA 。当时我们实现的方法是使用 declare ,今天,我们要学习的,则是 Swoole 提供的一套定时器工具。

Timer 定时器

Swoole 中提供的这个 Timer 定时器,底层是基于 epoll_wait 和 settitimer 实现的,数据结构使用最小堆,全部为内存操作,没有 IO 消耗。如果这些名词搞不明白的话,那么就记住最后一句话,它的性能非常高。

官方有提供一个基准测试脚本,添加或删除 10 万个随机时间的定时器耗时仅为 0.08s 左右,性能非常强悍。如果要与我们之前讲过的 declare 对比的话,那么这个 Swoole 提供的定时器,能够支持到毫秒级别、能够同时设定多个定时器,性能也更强悍。

说了这么多,其实我们就早就用过定时器了。在讲进程的时候,为了挂起不阻塞的 wait 监听,我们用得是什么?还记得吗?【Swoole系列3.3】单进程管理Process​​https://mp.weixin.qq.com/s/PulzY6Z0cXZocLuT-bqn2w​​ 。

定时器使用

讲了那么多好处,这个定时器怎么用呢?它与 JS 的 setInterval 以及 setTimeout 非常类似,也分为两种操作。

Tick

Tick 操作就是类似于 setInterval 的操作,是一个标准的定时器,设定后就开始定时执行。

$tickA = \Swoole\Timer::tick(1000, function($timer_id, $param1){
   static $i = 0;
   echo $param1 . ": ". microtime(true), PHP_EOL;
   $i++;
   if($i == 3){
       \Swoole\Timer::clear($timer_id);
   }
}, 'A');
//A: 1641218219.607
//A: 1641218220.6068
//A: 1641218221.6071      

\Swoole\Timer 就是 Swoole 提供的定时器类,它的所有操作都是静态方法。

tick() 方法就是一个定时器方法,第一个参数是定时的秒数,注意,这里是毫秒,所以我们上面的测试代码其实就是 1000 毫秒表示 1 秒。后面的回调函数是每间隔 1 秒后要执行的内容,它有两个回调参数。第一个参数是定时器的 id ,第二个参数是我们可以通过定时器传递给这个回调函数的参数内容。

比如说,我们给 tick() 方法的第三个参数传递了一个 A ,那么在回调函数中,我们也可以通过参数的方式接收到这个 A 。这个参数内外都是 ... 形式的多参构造,可以不限数量地传递参数。

接着在回调方法体内部,我们设定了一个计数器 ​

​$i​

​ ,为什么是一个静态属性呢?之前都讲过的哦。如果不记得了或者没看过我们之前讲过的 【PHP中的static】https://mp.weixin.qq.com/s/vJc2lXnIg7GCgPkrTh_xsw ,那么最好还是再去复习一下哦。

当 ​

​$i​

​ 达到三次以后,正常情况下也是过了 3 秒之后,我们使用一个 \Swoole\Timer::clear() 来清除定时器。

After

除了 tick() 方法之外,还有一个和 setTimeout 非常类似的方法,叫做 after() 方法。

$tickB = \Swoole\Timer::tick(1000, function($timer_id, $param1){
   echo $param1 . ": ". microtime(true), PHP_EOL;
}, 'B');

\Swoole\Timer::after(5000, function() use($tickB){
   \Swoole\Timer::clear($tickB);
});
//B: 1641218178.0143
//B: 1641218179.0137
//B: 1641218180.0136
//B: 1641218181.0131
//B: 1641218182.0136      

在这段代码中,我们先定义了一个 tick() ,然后通过它的返回值获得它的 timer_id 。接着,我们设定了一个 after() ,它表示的经过多长时间之后会执行指定的回调函数。同样,它的参数也是可以设置为毫秒的。我们这里设置的是 5 秒后,执行回调函数中的 clear() 方法去清除上面定义的那个 tick() 。

after() 只运行一次,就是在指定的时间之后运行,而 tick() 是一直不停地按指定的时间运行。这个其实就是 JS 中 setTimeout 和 setInterval 区别。

另外,我们还可以通过一个方法一次性地清理所有的 tick() 和 after() 。

\Swoole\Timer::tick(1000, function($timer_id, $param1){
   echo $param1 . ": ". microtime(true), PHP_EOL;
}, 'C');
\Swoole\Timer::tick(1000, function($timer_id, $param1){
   echo $param1 . ": ". microtime(true), PHP_EOL;
}, 'D');
\Swoole\Timer::after(5000, function(){
   echo "After: ". microtime(true), PHP_EOL;
});

\Swoole\Timer::after(3000, function(){
   \Swoole\Timer::clearAll();
});
//C: 1641222879.0361
//D: 1641222879.0362
//D: 1641222880.0356
//C: 1641222880.0357
//C: 1641222881.034
//D: 1641222881.0341      

首先是两个 tick() 会持续运行,接着是一个 after() ,会在五秒后运行,最后一个 after() 则调用了一个 clearAll() 清除所有的定时器。于是,最后的效果就如同注释中的一样,只有上面两个 tick() 运行了三次,之后包含那个等待五秒的 after() 也被一起清除了。

其它方法

剩下的东西其实没什么了,只是一些查看定时器内容的方法函数。

$tickE = \Swoole\Timer::tick(10000, function($timer_id, $param1){
   echo $param1 . ": ". microtime(true), PHP_EOL;
}, 'E');
\Swoole\Timer::tick(10000, function($timer_id, $param1){
   echo $param1 . ": ". microtime(true), PHP_EOL;
}, 'F');

var_dump(\Swoole\Timer::info($tickE));
//array(5) {
//  ["exec_msec"]=>
//  int(10000)
//  ["exec_count"]=>
//  int(0)
//  ["interval"]=>
//  int(10000)
//  ["round"]=>
//  int(0)
//  ["removed"]=>
//  bool(false)
//}

foreach (Swoole\Timer::list() as $timer_id) {
   var_dump(Swoole\Timer::info($timer_id));
   var_dump(Swoole\Timer::stats($timer_id));
}
//array(5) {
//  ["exec_msec"]=>
//  int(10000)
//  ["exec_count"]=>
//  int(0)
//  ["interval"]=>
//  int(10000)
//  ["round"]=>
//  int(0)
//  ["removed"]=>
//  bool(false)
//}
//array(3) {
//  ["initialized"]=>
//  bool(true)
//  ["num"]=>
//  int(2)
//  ["round"]=>
//  int(0)
//}
// ……………………
// ……………………      

Swoole\Timer::info() 方法打印定时器的信息,包括执行次数、间隔时间、移除信息等。Swoole\Timer::list() 可以输出当前进程中的所有定时器 id 列表。Swoole\Timer::stats() 用于查看定时器的状态信息。

除了这三个之外,我们还要关注的是 tick()、after()、clear() 都提供了一个函数风格别名,可以直接使用,见到它们的时候可别懵圈了。

类静态方法 函数风格别名
Swoole\Timer::tick() swoole_timer_tick()
Swoole\Timer::after() swoole_timer_after()
Swoole\Timer::clear() swoole_timer_clear()

总结

今天的内容非常简单,但却非常有用。有了这个定时器,其实我们就能做很多事情了,比如说常驻内存的定时任务。另外能够精确到毫秒级别也是非常强大的一个优势,毕竟速度和性能是 Swoole 区别于传统 PHP 开发的最大亮点。

测试代码: