学习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 = 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;
$next_run_time = -$scheduler_data;
$time_now = microtime(true);
$this->selectTimeout = ($next_run_time - $time_now) * 1000000;
if ($this->selectTimeout <= 0) {
$this->scheduler->extract();
if (!isset($this->eventTimer)) {
continue;
}
//
$task_data = $this->eventTimer;
if ($task_data === self::EV_TIMER) {
$next_run_time = $time_now + $task_data;
$this->scheduler->insert($timer_id, -$next_run_time);
}
call_user_func_array($task_data, $task_data);
if (isset($this->eventTimer) && $task_data === 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);
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();
牛逼,学习了
学习了
stream_select 在这里的作用我不是很了解,但我看代码感觉这个本质上就是用PHP的优先级队列,不断循环检查最小优先级的队列是否符合时间,这种死循环的形式,CPU占用应该还是比较大的吧,整体上并不是无阻塞的,在要求不高的场景上可以用,但不能堪以大用。跟 Workerman 的定时器还是差很多的。
这个本来就是workerman上抄下来的。
当然workerman不仅仅只是做了这些处理,我只是抄了其中一个,如果有libevent、event扩展,就不会走stream_select了
我再解释下:
1、定时器实现,一般可以使时间轮、时间堆,上面就是时间堆机制
2、php的优先级队列很适合做,就不需要自己写最小堆了。
3、stream_select超时 ,兼容win。
请教一下,如果其中一个定时器出现执行阻塞,那这个不就不准了吗
对的,定时器只是一个触发机制,定时触发,简单的运算,可以,如果是繁重的任务,或者可能出现长时间的阻塞的话,可以先起一些任务进程,定时器触发后,发送异步任务到任务进程去做,避免阻塞定时器的运行。