编写网站代码,用手机制作表格的软件,苏州首页关键词优化,西安建站网站原文首发链接#xff1a;Swoole 源码分析之 Timer 定时器模块 大家好#xff0c;我是码农先森。
引言
Swoole 中的毫秒精度的定时器。底层基于 epoll_wait 和 setitimer 实现#xff0c;数据结构使用最小堆#xff0c;可支持添加大量定时器。
在同步 IO 进程中使用 seti…原文首发链接Swoole 源码分析之 Timer 定时器模块 大家好我是码农先森。
引言
Swoole 中的毫秒精度的定时器。底层基于 epoll_wait 和 setitimer 实现数据结构使用最小堆可支持添加大量定时器。
在同步 IO 进程中使用 setitimer 和信号实现如 Manager 和 TaskWorker 进程在异步 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_after、swoole_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 中只支持到秒级别。数据结构使用最小堆支持添加大量定时器全部为内存操作且十分高效。定时器在实际的业务场景中应用也是非常广泛常用于延时或定时执行的任务中例如订单超时未付款自动取消等场景。