08 | Swoole 源码分析之 Timer 定时器模块

原文首发链接:Swoole 源码分析之 Timer 定时器模块
大家好,我是码农先森。

引言

Swoole 中的毫秒精度的定时器。底层基于 epoll_waitsetitimer 实现,数据结构使用最小堆,可支持添加大量定时器。

在同步 IO 进程中使用 setitimer 和信号实现,如 ManagerTaskWorker 进程,在异步 IO 进程中使用 epoll_wait/kevent/poll/select 超时时间实现。

定时器的添加和删除,全部为内存操作。在官方的基准测试脚本中,添加或删除 10 万个随机时间的定时器耗时为 0.08s 左右,因此性能是非常高效的。

源码拆解

我们在分析源代码之前,先看这段使用定时器的代码。Timer::after 函数是设置一个一次性的定时器,也就是执行一次就结束了,常用于执行一次性任务的场景。Timer::tick 函数会每间隔一段时间执行一次,类似一个闹钟的机制,常用于需要定时执行任务的场景。

<?php
// 设置一个一次性定时器
Swoole\Timer::after(1000, function(){
    echo " timer after timeout\n";
});

// 设置一个间隔时钟定时器
Swoole\Timer::tick(1000, function(){
    echo "timer tick timeout\n";
});

按照之前分析源代码的策略,先对整个源码的调用流程进行梳理,以便于让我们有个整体的印象,调用流程如下图所示。

swoole_timer.cc 这个源码文件中定义了两个函数 swoole_timer_afterswoole_timer_tick。从这段代码中可以看出唯一的区别是,在调用 timer_add 函数时的传参有所不同,一个是 false,一个是 true,表示的是是否需要持久化的执行任务。另外 timer_add 函数实现了一些根据细化的逻辑,例如:参数的解析、一些检查判断的工作。最后,根据 persistent 参数判断是否执行持久化的操作。

// 定义 PHP 函数 swoole_timer_after
// swoole-src/ext-src/swoole_timer.cc:221
static PHP_FUNCTION(swoole_timer_after) {
    timer_add(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
}

// 定义 PHP 函数 swoole_timer_tick
// swoole-src/ext-src/swoole_timer.cc:225
static PHP_FUNCTION(swoole_timer_tick) {
    timer_add(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
}

// 添加定时任务到定时器中, 并根据持久性标志判断是否需要一直执行
// swoole-src/ext-src/swoole_timer.cc:155
static void timer_add(INTERNAL_FUNCTION_PARAMETERS, bool persistent) {
    zend_long ms;
    Function *fci = (Function *) ecalloc(1, sizeof(Function));
    TimerNode *tnode;
    
    // 解析参数
    ZEND_PARSE_PARAMETERS_START(2, -1)
    Z_PARAM_LONG(ms)
    Z_PARAM_FUNC(fci->fci, fci->fci_cache)
    Z_PARAM_VARIADIC('*', fci->fci.params, fci->fci.param_count)
    ZEND_PARSE_PARAMETERS_END_EX(goto _failed);

    // 检查定时器值 ms 是否小于预定义的最小值 SW_TIMER_MIN_MS
    if (UNEXPECTED(ms < SW_TIMER_MIN_MS)) {
        php_swoole_fatal_error(E_WARNING, "Timer must be greater than or equal to " ZEND_TOSTR(SW_TIMER_MIN_MS));
    _failed:
        efree(fci);
        RETURN_FALSE;
    }

	// 进行额外的检查
    // no server || user worker || task process with async mode
    if (!sw_server() || sw_server()->is_user_worker() ||
        (sw_server()->is_task_worker() && sw_server()->task_enable_coroutine)) {
        php_swoole_check_reactor();
    }

    // 使用指定的毫秒数、持久性标志、回调函数 timer_callback 和函数指针 fci 添加一个定时器
    tnode = swoole_timer_add((long) ms, persistent, timer_callback, fci);
    if (UNEXPECTED(!tnode)) {
        php_swoole_fatal_error(E_WARNING, "add timer failed");
        goto _failed;
    }
	
	// 为定时器节点 tnode 设置类型和析构函数
    tnode->type = TimerNode::TYPE_PHP;
    tnode->destructor = timer_dtor;

    // 根据持久性标志,会一直执行定时的任务
    if (persistent) {
        if (fci->fci.param_count > 0) {
            uint32_t i;
            zval *params = (zval *) ecalloc(fci->fci.param_count + 1, sizeof(zval));
            for (i = 0; i < fci->fci.param_count; i++) {
                ZVAL_COPY(&params[i + 1], &fci->fci.params[i]);
            }
            fci->fci.params = params;
        } else {
            fci->fci.params = (zval *) emalloc(sizeof(zval));
        }
        fci->fci.param_count += 1;
        ZVAL_LONG(fci->fci.params, tnode->id);
    } else {
    	// 只会执行一次
        sw_zend_fci_params_persist(&fci->fci);
    }
    sw_zend_fci_cache_persist(&fci->fci_cache);
    RETURN_LONG(tnode->id);
}

timer.cc 源码文件中 swoole_timer_add 这个函数会检查是否已经有可用的定时器管理对象,如果没有的话会进行实例化创建一个,然后通过 SwooleTG.timer->add() 方法添加一个定时器任务。

// 这段代码用于添加一个定时器到 Swoole 框架中的定时器管理器中
// swoole-src/src/wrapper/timer.cc:40
TimerNode *swoole_timer_add(long ms, bool persistent, const TimerCallback &callback, void *private_data) {
    // 这里检查定时器是否可用
    if (sw_unlikely(!swoole_timer_is_available())) {
    	// 如果定时器不可用,则会创建一个新的对象
        SwooleTG.timer = new Timer();
        // 并对其进行初始化
        if (sw_unlikely(!SwooleTG.timer->init())) {
            // 若初始化失败,就会释放内存
            delete SwooleTG.timer;
            SwooleTG.timer = nullptr;
            return nullptr;
        }
    }
    // 调用定时器对象的 add 方法,向定时器中添加一个定时器
    return SwooleTG.timer->add(ms, persistent, private_data, callback);
}

这个函数 *Timer::add 会构建一个新的定时器节点,并且设置一些属性值,例如:类型、执行时间、回调函数等。最后,会将定时器节点加入到最小堆的数据结构中。

// 用于向定时器管理器中添加一个新的定时器节点
// swoole-src/src/core/timer.cc:106
TimerNode *Timer::add(long _msec, bool persistent, void *data, const TimerCallback &callback) {
    // 检查传入的毫秒数 _msec 是否小于等于 0
    if (sw_unlikely(_msec <= 0)) {
        swoole_error_log(SW_LOG_WARNING, SW_ERROR_INVALID_PARAMS, "msec value[%ld] is invalid", _msec);
        return nullptr;
    }

	// 获取当前相对毫秒数,并检查其是否小于 0
    int64_t now_msec = get_relative_msec();
    if (sw_unlikely(now_msec < 0)) {
        return nullptr;
    }
	
	// 创建一个新的定时器节点 tnode
	// 并设置节点的数据、类型、执行时间、间隔、状态、回调函数、轮数以及析构函数
    TimerNode *tnode = new TimerNode();
    tnode->data = data;
    tnode->type = TimerNode::TYPE_KERNEL;
    tnode->exec_msec = now_msec + _msec;
    tnode->interval = persistent ? _msec : 0;
    tnode->removed = false;
    tnode->callback = callback;
    tnode->round = round;
    tnode->destructor = nullptr;

	// 更新下一个计划触发时间
	// 如果当前没有下一个计划或者新的时间比当前下一个计划更早
	// 则更新为新的时间。
    if (next_msec_ < 0 || next_msec_ > _msec) {
        set(this, _msec);
        next_msec_ = _msec;
    }

	// 给定时器节点分配一个唯一的ID
    tnode->id = _next_id++;
    if (sw_unlikely(tnode->id < 0)) {
        tnode->id = 1;
        _next_id = 2;
    }
	
	// 将节点加入堆中,同时更新堆的索引
    tnode->heap_node = heap.push(tnode->exec_msec, tnode);
    if (sw_unlikely(tnode->heap_node == nullptr)) {
        delete tnode;
        return nullptr;
    }

    // 记录节点信息
    map.emplace(std::make_pair(tnode->id, tnode));
    swoole_trace_log(SW_TRACE_TIMER,
                     "id=%ld, exec_msec=%" PRId64 ", msec=%ld, round=%" PRIu64 ", exist=%lu",
                     tnode->id,
                     tnode->exec_msec,
                     _msec,
                     tnode->round,
                     count());
                     
    // 返回新添加的定时器节点
    return tnode;
}

总结

  • Swoole 中实现了毫秒精度的定时器,而原生的 PHP 中只支持到秒级别。
  • 数据结构使用最小堆支持添加大量定时器,全部为内存操作且十分高效。
  • 定时器在实际的业务场景中应用也是非常广泛,常用于延时或定时执行的任务中,例如:订单超时未付款自动取消等场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/524120.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据库系统概论(超详解!!!)第三节 关系数据库标准语言SQL(Ⅵ)

1.空值的处理 空值就是“不知道”或“不存在”或“无意义”的值。 一般有以下几种情况&#xff1a; 该属性应该有一个值&#xff0c;但目前不知道它的具体值 &#xff1b;该属性不应该有值 &#xff1b;由于某种原因不便于填写。 1.空值的产生 空值是一个很特殊的值&#x…

什么牌子开放式耳机好用?优选五大高分好物真诚分享

对于习惯长时间佩戴耳机的朋友来说&#xff0c;入耳式耳机固然能够提供较优质的音质体验。但是&#xff0c;由于其较为封闭的设计以及对耳洞的压迫&#xff0c;舒适感较差&#xff0c;长时间佩戴可能会对听力造成一定的影响。因此&#xff0c;开放式耳机的出现为音乐发烧友们提…

青风环境带您了解2024第13届生物发酵展

参展企业介绍 浙江青风环境股份有限公司创立于1998年&#xff0c;是一家集科研、生产及贸易为一体的高新技术企业。公司座落于浙江省丽水市水阁工业区&#xff0c;占地面积120亩&#xff0c;建筑面积近11万平方米&#xff0c;年产值可达20亿元&#xff0c;建有标准的冷&#x…

回归预测 | Matlab实现WOA-GPR鲸鱼算法优化高斯过程回归多变量回归预测

回归预测 | Matlab实现WOA-GPR鲸鱼算法优化高斯过程回归多变量回归预测 目录 回归预测 | Matlab实现WOA-GPR鲸鱼算法优化高斯过程回归多变量回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现WOA-GPR鲸鱼算法优化高斯过程回归多变量回归预测 1.Matlab实现…

期货学习笔记-MACD指标学习2

MACD底背离把握买入多单的技巧 底背离的概念及特征 底背离指的是MACD指标与价格低点之间的对比关系&#xff0c;这里需要明白的是MACD指标的涨跌动能和价格形态衰竭形态之间的关系&#xff0c;如果市场价格创新低而出现衰竭形态同时也有底背离形态的出现&#xff0c;此时下跌…

Github项目推荐-ChatGPT-Admin-Web

项目地址 https://github.com/AprilNEA/ChatGPT-Admin-Web 项目简介 通过api接入大模型&#xff0c;并基于此封装了一层用户管理的功能&#xff0c;适合团队内使用。 项目截图

【Easy云盘 | 第三篇】登录注册模块上篇(获取验证码、发送邮箱验证码、登录、注册、重置密码)基于AOP实现参数校验

文章目录 4.2登录注册模块设计4.2.1获取验证码&#xff08;1&#xff09;思路&#xff08;2&#xff09;接口&#xff08;3&#xff09;controller层&#xff08;4&#xff09;CreateImageCodeUtils工具类&#xff08;5&#xff09;测试结果 4.2.2发送邮箱验证码&#xff08;1&…

Chapter 1 Basic Concepts of Communication and Communication Systems

1.1 The Concept of Communication communication【通信】:It is the process of using signals to transmit messages containing information in space. To put it simply, communication is the spatial transmission of information【信息的空间传递】Information【信息】…

LeetCode-46. 全排列【数组 回溯】

LeetCode-46. 全排列【数组 回溯】 题目描述&#xff1a;解题思路一&#xff1a;回溯。回溯三部曲解题思路二&#xff1a;0解题思路三&#xff1a;0 题目描述&#xff1a; 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案…

信息收集之内网渗透(二)

★★免责声明★★ 文章中涉及的程序(方法)可能带有攻击性&#xff0c;仅供安全研究与学习之用&#xff0c;读者将信息做其他用途&#xff0c;由Ta承担全部法律及连带责任&#xff0c;文章作者不承担任何法律及连带责任。 0、前言 本文主要是一些命令的集合&#xff0c;会比较枯…

PCL 点到三角形的距离(3D)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 给定三角形ABC和点P,设Q为描述ABC上离P最近的点。求Q的一个方法:如果P在ABC内,那么P的正交投影点就是离P最近的点Q。如果P投影在ABC之外,最近的点则必须位于它的一条边上。在这种情况下,Q可以通过计算线段AB、…

【算法】动态规划练习(一)

目录 1137. 第 N 个泰波那契数 分析 代码 面试题 08.01. 三步问题 分析 代码 746. 使用最小花费爬楼梯 分析 代码 泰波那契序列 Tn 定义如下&#xff1a; T0 0, T1 1, T2 1, 且在 n > 0 的条件下 Tn3 Tn Tn1 Tn2 给你整数 n&#xff0c;请返回第 n 个泰波…

Redis: 持久化

文章目录 一、RDB持久化1、概念2、执行时机&#xff08;1&#xff09; 执行save命令&#xff08;2&#xff09;执行bgsave命令&#xff08;3&#xff09;Redis停机时&#xff08;4&#xff09;触发RDB条件 3、原理4、小结 二、AOF持久化1、概念2、AOF配置3、AOF文件重写4、RDB与…

中文地址分词器源码阅读(jiedi)

文章目录 structure.p文件pd.read_excelenumerate思维导图核心源码讲解jiedi.pytrain.py 总结 structure 点击左边的Structure按钮就如Structure界面。从Structure我们可以看出当前代码文件中有多少个全局变量、函数、类以及类中有多少个成员变量和成员函数。 其中V图标表示全…

HFSS仿真环形耦合器学习笔记

HFSS仿真环形耦合器学习笔记 文章目录 HFSS仿真环形耦合器学习笔记1、 理论基础2、 设计分析3、 仿真验证1、 求解器设置2、 建模3、 激励方式设置4、 边界条件设置5、 扫频设置6、 设计检查&#xff0c;仿真分析7、 数据后处理 1、 理论基础 环形定向耦合器的结构示意图如图所…

三分钟带你了解,可重构柔性装配生产线

产品个性化时代&#xff0c;产品小批量、多批次&#xff0c;行业常用高柔性的人-机混合装配线实现跨品类产品装配&#xff0c;但产品的装配质量一致性差、效率低成为行业痛点。富唯智能联合清华大学提出了可重构柔性装配方法和技术&#xff0c;实现跨品类产品的数控自动化装配。…

Spring 源码学习笔记(一)之搭建源码环境

前言 一直以来对 Spring 源码的理解不够全面&#xff0c;也不成条理&#xff0c;只是对其中的某小部分比较了解&#xff0c;所以从今天开始要重新系统学习 Spring 的源码了。 搭建源码环境 首先需要说明的是&#xff0c;源码环境并不是必须的&#xff0c;搭建源码环境唯一的好…

代码随想录算法训练营第四十六天 |139. 单词拆分 、卡码网56. 携带矿石资源

代码随想录算法训练营第四十六天 |139. 单词拆分 、卡码网56. 携带矿石资源 139. 单词拆分题目解法 卡码网56. 携带矿石资源题目解法 背包总结感悟 139. 单词拆分 题目 解法 题解链接 1. class Solution { public:bool wordBreak(string s, vector<string>& wordD…

JUC_1

进程 概述 进程&#xff1a;程序是静止的&#xff0c;进程实体的运行过程就是进程&#xff0c;是系统进行资源分配的基本单位 进程的特征&#xff1a;并发性、异步性、动态性、独立性、结构性 线程&#xff1a;线程是属于进程的&#xff0c;是一个基本的 CPU 执行单元&#x…

主机名控制者:DNS服务器

文章目录 什么是DNS用网络主机名取得IP的历史渊源DNS的主机名对应IP的查询流程合法DNS的关键&#xff0c;申请区域查询授权DNS数据库的记录&#xff1a;正解、反解、Zone的意义DNS数据库的类型&#xff1a;hint、Master/Slave架构 Client端的设置相关配置文件DNS的正、反解查询…