php毫秒定时器,也支持win

学习workerman源码,研究了定时器部分,抄了一个定时器类出来,分享出来,调用方式一样。
<?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();
已邀请:

静默

赞同来自:

牛逼,学习了

keytehu

赞同来自:

学习了

pader - phper

赞同来自:

stream_select 在这里的作用我不是很了解,但我看代码感觉这个本质上就是用PHP的优先级队列,不断循环检查最小优先级的队列是否符合时间,这种死循环的形式,CPU占用应该还是比较大的吧,整体上并不是无阻塞的,在要求不高的场景上可以用,但不能堪以大用。跟 Workerman 的定时器还是差很多的。

dazhaozhao

赞同来自:

我再解释下:
1、定时器实现,一般可以使时间轮、时间堆,上面就是时间堆机制
2、php的优先级队列很适合做,就不需要自己写最小堆了。
3、stream_select超时 ,兼容win。

要回复问题请先登录注册