php实现毫秒定时器(时间堆)
最近研究workerman源码,学习了定时器部分,从里面抄了一个定时器出来,单独出来,有利于理解定时器原理。
下面是代码,调用方式跟workerman一样,都是添加定时事件进去,可以使延时调用一次,也可以是定时调用,间隔一段事件就执行一次。测试执行,在命令行里面,执行php timer.php即可。
<?php
class Timer
{
const EV_TIMER = 1;
const EV_TIMER_ONCE = 2;
protected $scheduler = null;
protected $eventTimer = array();
public $timerId = 1;
protected $selectTimeout = 100000000;
protected $socket = array();
public function __construct()
{
$this->socket = stream_socket_pair(
DIRECTORY_SEPARATOR === '/' ?
STREAM_PF_UNIX : STREAM_PF_INET, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
$this->scheduler = new \SplPriorityQueue();
$this->scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
}
public function add($fd, $flag, $func, $args = array())
{
$timer_id = $this->timerId++;
$run_time = microtime(true) + $fd;
$this->scheduler->insert($timer_id, -$run_time);
$this->eventTimer[$timer_id] = array($func, (array) $args, $flag, $fd);
$select_timeout = ($run_time - microtime(true)) * 1000000;
if ($this->selectTimeout > $select_timeout) {
$this->selectTimeout = $select_timeout;
}
return $timer_id;
}
public function loop()
{
while (1) {
$read = $this->socket;
set_error_handler(function () {});
$ret = stream_select($read, $write = [], $except = [], 0, $this->selectTimeout);
restore_error_handler();
if (!$this->scheduler->isEmpty()) {
$this->tick();
}
}
}
public function getTimerCount()
{
return count($this->eventTimer);
}
/**
* Tick for timer.
*
* @return void
*/
protected function tick()
{
while (!$this->scheduler->isEmpty()) {
$scheduler_data = $this->scheduler->top();
$timer_id = $scheduler_data['data'];
$next_run_time = -$scheduler_data['priority'];
$time_now = microtime(true);
$this->selectTimeout = ($next_run_time - $time_now) * 1000000;
if ($this->selectTimeout <= 0) {
$this->scheduler->extract();
if (!isset($this->eventTimer[$timer_id])) {
continue;
}
// [func, args, flag, timer_interval]
$task_data = $this->eventTimer[$timer_id];
if ($task_data[2] === self::EV_TIMER) {
$next_run_time = $time_now + $task_data[3];
$this->scheduler->insert($timer_id, -$next_run_time);
}
call_user_func_array($task_data[0], $task_data[1]);
if (isset($this->eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) {
$this->del($timer_id, self::EV_TIMER_ONCE);
}
continue;
}
return;
}
$this->selectTimeout = 100000000;
}
/**
* {@inheritdoc}
*/
public function del($fd, $flag)
{
$fd_key = (int) $fd;
unset($this->eventTimer[$fd_key]);
return true;
}
/**
* {@inheritdoc}
*/
public function clearAllTimer()
{
$this->scheduler = new \SplPriorityQueue();
$this->scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
$this->eventTimer = array();
}
}
function microtime_float()
{
list($usec, $sec) = explode(" ", microtime());
return bcadd($usec, $sec, 3);
}
$timer = new Timer();
$timer->add(1, Timer::EV_TIMER, function () {
echo microtime_float() . "\n";
});
$timer->add(1, Timer::EV_TIMER_ONCE, function () {
echo microtime_float() . "once \n";
});
$timer->loop();
可以在win下执行,不依赖其他扩展,直接装上php就可以运行,不过生产环境,还是建议部署在linux上,此定时器,加上守护进程,就可以做一些定时任务,支持毫秒定时,不过可能会有1毫秒的误差,相比linux的crontab机制,可以更精确定时。
相关的原理:
1、基于stream_select的超时机制
2、基于时间堆(最小堆)
3、使用了php的spl里面的优先队列,SplPriorityQueue
相关资料:
3、优先队列
文章版权声明:除非注明,否则均为彭超的博客原创文章,转载或复制请以超链接形式并注明出处。
继续浏览有关 php 的文章
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。