php实现毫秒定时器(时间堆)

时间:2019-08-12作者:klpeng分类:IT综合浏览:1580评论:0

        最近研究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();

php实现毫秒定时器(时间堆)

可以在win下执行,不依赖其他扩展,直接装上php就可以运行,不过生产环境,还是建议部署在linux上,此定时器,加上守护进程,就可以做一些定时任务,支持毫秒定时,不过可能会有1毫秒的误差,相比linux的crontab机制,可以更精确定时。

相关的原理:

1、基于stream_select的超时机制

2、基于时间堆(最小堆)

3、使用了php的spl里面的优先队列,SplPriorityQueue

相关资料:

1、workerman

2、【高性能定时器】时间堆

3、优先队列

 

 

打赏
文章版权声明:除非注明,否则均为彭超的博客原创文章,转载或复制请以超链接形式并注明出处。
上一篇:php解析yaml 下一篇:php实现守护进程
相关推荐

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

猜你喜欢