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_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.mzph.cn/news/798543.shtml

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

相关文章

今年过去了多少天?(switch)

//今年已经过去了几天&#xff1f; #include <stdio.h> int monthday(int year,int month){switch(month){case 1:return 31;case 2:if ((year % 4 0 && year % 100 ! 0)||year % 400 0){return 29;}else{return 28;}break;case 3:return 31;case 4:return 30;…

C语言进阶课程学习记录-第24课 - #pragma 使用分析

C语言进阶课程学习记录-第24课 - #pragma 使用分析 #pragma实验-#pragma messagecmd窗口运行 实验-pragma oncebcc编译报错gcc编译成功global.h代码优化 #pragma pack实验BCC编译器输出 小结 本文学习自狄泰软件学院 唐佐林老师的 C语言进阶课程&#xff0c;图片全部来源于课程…

k8s1(1),Linux运维基础开发与实践

#设置主机名 hostnamectl hostnameXXX #配置免密(包括操作机) ssh-keygen ssh-copy-id master*/slave* #传输hosts cat > /etc/hosts <<EOF 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain loca…

【Qt 学习笔记】Qt 中出现乱码的解释及讨论

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt 中出现乱码的解释及讨论 文章编号&#xff1a;Qt 学习笔记 / 06 文…

工单派单-saas工单处理软件效益分析,智能解决企业管理痛点亿发

企业对引入工单管理系统是有迫切需求的&#xff0c;工单管理系统可以有效地管理任务和工作流程&#xff0c;提高工作效率和客户满意度。 在没有工单管理系统之前&#xff0c;许多企业可能面临着诸如任务分配不清晰、信息不透明、工作流程混乱等管理挑战。举例来说&#xff0c;…

C#.手术麻醉系统源码 手麻系统如何与医院信息系统进行集成?

C#.手术麻醉系统源码 手麻系统如何与医院信息系统进行集成&#xff1f; 手术麻醉系统与医院信息系统的集成是一个关键步骤&#xff0c;它有助于实现信息的共享和流程的协同&#xff0c;从而提高医疗服务的效率和质量。手麻系统与lis、his、pacs等系统的对接是医院信息化建设的重…

Leetcode 148. 排序链表

心路历程&#xff1a; 这道题通过很简单&#xff0c;但是如果想要用O(1)的空间复杂度O(nlogn)的时间复杂度的话&#xff0c;可能得需要双指针快排的思路。 解法&#xff1a;遍历模拟 # Definition for singly-linked list. # class ListNode: # def __init__(self, val0…

Java基础入门--Java API课后题

五、编程题 1.编写一个每次随机生成 10个 0&#xff08;包括&#xff09; 到 100 之间的随机正整数。 import java.util.Random;public class Example01{public static void main(String[] args) {for(int i0;i<10;i) {System.out.println(new Random().nextInt(0,100));}}…

【黑马头条】-day05延迟队列文章发布审核-Redis-zSet实现延迟队列-Feign远程调用

文章目录 昨日回顾今日内容1 延迟任务1.1 概述1.2 技术对比1.2.1 DelayQueue1.2.2 RabbitMQ1.2.3 Redis实现1.2.4 总结 2 redis实现延迟任务2.0 实现思路2.1 思考2.2 初步配置实现2.2.1 导入heima-leadnews-schedule模块2.2.2 在Nacos注册配置管理leadnews-schedule2.2.3 导入表…

MySQL事务以及并发访问隔离级别

MySQL事务以及并发问题 事务1.什么是事务2.MySQL如何开启事务3.事务提交方式4.事务原理5.事务的四大特性&#xff08;ACID&#xff09; 事务并发问题1.并发引起的三个问题2.事务隔离级别 事务 在 MySQL 中&#xff0c;事务支持是在引擎层实现的。MySQL 是一个支持多引擎的系统&…

Java开发测试(第一篇):Java测试框架JUnit5

目录 1.基本介绍 2.maven中安装JUnit5 3.使用 4.JUnit5命名规则 5.JUnit5常用注解 6.JUnit5断言 7.JUnit5多个类之间的继承关系 8.JUnit5参数化 &#xff08;1&#xff09;使用场景&#xff1a; &#xff08;2&#xff09;使用前需在pom.xml文件中导入依赖 &#xff…

AcWing 1388. 游戏(每日一题)

原题链接&#xff1a;1388. 游戏 - AcWing题库 玩家一和玩家二共同玩一个小游戏。 给定一个包含 N 个正整数的序列。 由玩家一开始&#xff0c;双方交替行动。 每次行动可以在数列的两端之中任选一个数字将其取走&#xff0c;并给自己增加相应数字的分数。&#xff08;双方…

TCP三次握手,四次挥手

TCP为什么四次挥手&#xff1f;而不是三次&#xff1f; 正常流程&#xff1a;服务接收到 客户端的 FIN请求后&#xff0c;会发送一个ACK响应&#xff0c;等待系统资源释放后&#xff0c;再发送FIN 请求给客户端&#xff0c;客户端再发送一个ACK响应。 若为三次&#xff1a;就是…

unsigned和int相加减的错误

先看代码 #include<cstring> #include<vector> using namespace std; int main() {string s "12345678";int n s.length();cout << "n-10" << n - 10 << endl;cout << "s.length() - 10" << s.len…

第九届蓝桥杯大赛个人赛省赛(软件类)真题C 语言 A 组-航班时间

#include<iostream> using namespace std;int getTime(){int h1, h2, m1, m2, s1, s2, d 0;//d一定初始化为0&#xff0c;以正确处理不跨天的情况 scanf("%d:%d:%d %d:%d:%d (%d)", &h1, &m1, &s1, &h2, &m2, &s2, &d);return d …

基于Arduino nano配置银燕电调

1 目的 配置电调&#xff0c;设置电机转动方向&#xff0c;使得CW电机朝顺时针方向转动&#xff0c;CCW电机朝逆时针转动。 2 步骤 硬件 Arduino nano板子及USB线变阻器银燕电调EMAX Bullet 20A朗宇电机 2205 2300KV格氏电池3S杜邦线若干接线端子 软件 BLHeliSuite 注意…

数据库表设计18条黄金规则

前言 对于后端开发同学来说&#xff0c;访问数据库&#xff0c;是代码中必不可少的一个环节。 系统中收集到用户的核心数据&#xff0c;为了安全性&#xff0c;我们一般会存储到数据库&#xff0c;比如&#xff1a;mysql&#xff0c;oracle等。 后端开发的日常工作&#xff…

算法-数论-蓝桥杯

算法-数论 1、最大公约数 def gcd(a,b):if b 0:return areturn gcd(b, a%b) # a和b的最大公约数等于b与a mod b 的最大公约数def gcd(a,b):while b ! 0:cur aa bb cur%bpassreturn a欧几里得算法 a可以表示成a kb r&#xff08;a&#xff0c;b&#xff0c;k&#xff0c…

2024最新软件测试八股文,能不能拿心仪Offer就看你背得怎样了

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

Linux - mac 装 mutipass 获取 ubuntu

mutipass &#xff1a;https://multipass.run/docs/mac-tutorial mutipass list mutipass launch --name myname mutipass shell myname 获取 root权限&#xff1a; sudo su