php毫秒定时器,也支持win

dazhaozhao

学习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();
2644 4 0
4个回答

静默

牛逼,学习了

  • 暂无评论
keytehu

学习了

  • 暂无评论
pader

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

  • dazhaozhao 2019-08-13

    这个本来就是workerman上抄下来的。
    当然workerman不仅仅只是做了这些处理,我只是抄了其中一个,如果有libevent、event扩展,就不会走stream_select了

dazhaozhao

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

  • a392223903 2020-09-06

    请教一下,如果其中一个定时器出现执行阻塞,那这个不就不准了吗

  • dazhaozhao 2020-09-16

    对的,定时器只是一个触发机制,定时触发,简单的运算,可以,如果是繁重的任务,或者可能出现长时间的阻塞的话,可以先起一些任务进程,定时器触发后,发送异步任务到任务进程去做,避免阻塞定时器的运行。

年代过于久远,无法发表回答
🔝