【原创】关于workerman定时器的实现方式的问题

blogdaren 2022-04-06 抢沙发 162人次

问题背景:

1、详见workerman社区原贴: https://www.workerman.net/q/8196

2、作者只针对workerman主进程对于定时器实现的原理,通过剥离一切网络事件代码仿真实现一下。

个人结论:

1、对workerman的主进程而言,其定时器实现依赖的是alarm机制,换句话:pcntl_alarm是在给定的时间之后给当前进程发送时钟信号,同样也是需要主动调用dispatch去检测下信号的【这个函数在信号检测领域的任何时候都很重要】,workerman的主进程代码空间的大LOOP【详见Worker::monitorWorkersForLinux()】里的dispatch就是用来干这个事情的,和具体的事件轮询类没有任何关系; 

2、对workerman的子进程而言,其定时器实现才会依赖到各个网络事件库,这些网络事件库均有内置的定时器技术实现,上层都是调用一层层的封装API来实现的。

代码实战:

<?php
class Timerman
{
    /**
     * Tasks that based on ALARM signal.
     * [
     *   run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]],
     *   run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]],
     *   ..
     * ]
     *
     * @var array
     */
    protected static $_tasks = array();

    /**
     * Init.
     *
     * @return void
     */
    public static function init()
    {
        \pcntl_signal(\SIGALRM, array(self::class, 'signalHandler'), false);
    }

    /**
     * ALARM signal handler.
     *
     * @return void
     */
    public static function signalHandler()
    {
        \pcntl_alarm(1);
        self::tick();
    }

    /**
     * Add a timer.
     *
     * @param float    $time_interval
     * @param callable $func
     * @param mixed    $args
     * @param bool     $persistent
     * @return int|false
     */
    public static function add($time_interval, $func, $args = array(), $persistent = true)
    {
        if ($time_interval <= 0) {
            echo(new Exception("bad time_interval"));exit(PHP_EOL);
            return false;
        }

        if ($args === null) {
            $args = array();
        }

        if (!\is_callable($func)) {
            echo(new Exception("not callable"));exit(PHP_EOL);
            return false;
        }

        if (empty(self::$_tasks)) {
            \pcntl_alarm(1);
        }

        $run_time = \time() + $time_interval;
        if (!isset(self::$_tasks[$run_time])) {
            self::$_tasks[$run_time] = array();
        }
        self::$_tasks[$run_time][] = array($func, (array)$args, $persistent, $time_interval);

        return 1;
    }


    /**
     * Tick.
     *
     * @return void
     */
    public static function tick()
    {
        if (empty(self::$_tasks)) {
            \pcntl_alarm(0);
            return;
        }

        $time_now = \time();
        foreach (self::$_tasks as $run_time => $task_data) {
            if ($time_now >= $run_time) {
                foreach ($task_data as $index => $one_task) {
                    $task_func     = $one_task[0];
                    $task_args     = $one_task[1];
                    $persistent    = $one_task[2];
                    $time_interval = $one_task[3];
                    try {
                        \call_user_func_array($task_func, $task_args);
                    } catch (\Exception $e) {
                        echo($e);
                    }
                    if ($persistent) {
                        self::add($time_interval, $task_func, $task_args);
                    }
                }
                unset(self::$_tasks[$run_time]);
            }
        }
    }

    /**
     * Remove a timer.
     *
     * @param mixed $timer_id
     * @return bool
     */
    public static function del($timer_id)
    {
        return false;
    }

    /**
     * Remove all timers.
     *
     * @return void
     */
    public static function delAll()
    {
        self::$_tasks = array();
        \pcntl_alarm(0);
    }
}

//模拟触发定时器
Timerman::init();
$time_interval = 1;
Timerman::add($time_interval, function()use($time_interval){
    echo "[". date('Y-m-d H:i:s', time()) . "] 我是间隔{$time_interval}秒的定时任务......" . PHP_EOL;
}, [], true);

//模拟workerman主进程大循环
$i = 0;
while(true)
{
    //关键的来了:尝试的注释掉pcntl_signal_dispatch看看还好使么?
    pcntl_signal_dispatch();
    $pid = posix_getpid();
    if($i % 5 == 0) echo "只模拟下workerman主进程,我不是任务,不用管我,当前主进程ID是:" . $pid . PHP_EOL;
    $i++;

    //pcntl_wait()在这里的返回值永远是负数,因为咱们只是模拟主进程嘛,
    //并没有子进程一说,这么写仅仅是仿真workerman代码,没啥意义。
    $status = 0;
    $son_pid = pcntl_wait($status, \WUNTRACED);
    if($son_pid < 0) sleep(1);
} 

版权声明:除非注明,本文由( blogdaren )原创,转载请保留文章出处。

本文链接:【原创】关于workerman定时器的实现方式的问题

Free Web Hosting