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

news/发布时间2024/5/18 15:01:55

原文首发链接: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_MSif (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 modeif (!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 是否小于等于 0if (sw_unlikely(_msec <= 0)) {swoole_error_log(SW_LOG_WARNING, SW_ERROR_INVALID_PARAMS, "msec value[%ld] is invalid", _msec);return nullptr;}// 获取当前相对毫秒数,并检查其是否小于 0int64_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;}// 给定时器节点分配一个唯一的IDtnode->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 中只支持到秒级别。
  • 数据结构使用最小堆支持添加大量定时器,全部为内存操作且十分高效。
  • 定时器在实际的业务场景中应用也是非常广泛,常用于延时或定时执行的任务中,例如:订单超时未付款自动取消等场景。

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

如若内容造成侵权/违法违规/事实不符,请联系编程大学网进行投诉反馈email:xxxxxxxx@qq.com,一经查实,立即删除!

相关文章

手把手教你做阅读理解题-初中中考阅读理解解题技巧013-dearMars Project

PDF格式公众号回复关键字:ZKYD013阅读理解技巧,在帮助读者有效获取和理解文本信息方面发挥着重要作用,熟练掌握如下6个技巧,可快速突破阅读理解 1 预览文章结构 在开始深入阅读之前,快速浏览文章的标题、段落开头和结尾,可以迅速把握文章的主题、大致内容和结构 标题通常能…

Java登陆第四十天——Router路由守卫练习

需求 未登录无法访问除login页面 练习 1.使用vite创建项目,导入依赖 npm create vite 选择vue+js npm i 导入基本依赖 npm vue-router 导入路由依赖2. 创建组件,login.vue、home.vue、list.vue 仅展示home.vue组件,其他都一样。 <script setup></script><tem…

Nginx 解析漏洞复现

该漏洞与php和nginx版本无关,是配置错误导致的问题 漏洞描述 通常在nginx.conf的配置文件或者include包含的其他配置文件下有以下信息 location ~ \.php$ {fastcgi_index index.php;include fastcgi_params;fastcgi_param REDIRECT_STATUS 200;fastcgi_param SCRIPT_FILE…

阿里云服务器+NAS

什么是ECS ECS:即Elastic Compute Service 弹性计算(Elastic Computing)是一种云计算服务模型,它旨在提供灵活、自动且可伸缩的计算资源。弹性计算的关键特性包括:弹性伸缩: 用户可以根据实际需求自动调整计算资源的规模,实现按需分配和释放。这意味着在峰值时段增加资源…

uniapp 连接华为手机 usb调试 选择 音频来源

uniapp 连接华为手机 usb调试 选择 音频来源--------------------------------------------- 生活的意义就是你自己知道你要做什么,明确目标。没有目标,后面都是瞎扯! https://pengchenggang.gitee.io/navigator/ SMART原则:目标必须是具体的(Specific) 目标必须是可以衡…