深入出不来nodejs源码-timer模块(C++篇)
终于可以填上坑了。
简单回顾一下之前JS篇内容,每一次setTimeout的调用,会在一个对象中添加一个键值对,键为延迟时间,值为一个链表,将所有该时间对应的事件串起来,图如下:
而每一个延迟键值对的触发,则是在链表头生成的时候就已经开始了,如下:
function TimersList(msecs, unrefed) {
//... // 来源于C++内置模块
const timer = this._timer = new TimerWrap();
/// ... // 触发
timer.start(msecs);
}
回顾完毕。
与JS篇一样,这一节也简单介绍libuv内部的一个数据结构:二叉树。源码来源于:uv/src/heap-inl.h。
因为二叉树的介绍网上一堆,所以这里只看一下API,首先是节点:
struct heap_node {
struct heap_node* left;
struct heap_node* right;
struct heap_node* parent;
};
分别代表左右、父节点。
/* A binary min heap. The usual properties hold: the root is the lowest
* element in the set, the height of the tree is at most log2(nodes) and
* it's always a complete binary tree.
*
* The heap function try hard to detect corrupted tree nodes at the cost
* of a minor reduction in performance. Compile with -DNDEBUG to disable.
*/
struct heap {
struct heap_node* min;
unsigned int nelts;
};
这里的注释可以看一下,这个结构体是独立的,min指向当前树的最小值。
另外还有三个操作方法:
HEAP_EXPORT(void heap_insert(struct heap* heap,
struct heap_node* newnode,
heap_compare_fn less_than));
HEAP_EXPORT(void heap_remove(struct heap* heap,
struct heap_node* node,
heap_compare_fn less_than));
HEAP_EXPORT(void heap_dequeue(struct heap* heap, heap_compare_fn less_than));
分别代表树的插入、移除,以及将小于指定值的节点移除并重新整理树,实现自己去看,懒得讲。
进入正题,从JS的触发代码开始看。
废话不多说,直接进入timer_wrapper.cc看start方法,源码如下:
static void Start(const FunctionCallbackInfo<Value>& args) {
// 不管这3行
TimerWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); CHECK(HandleWrap::IsAlive(wrap));
// 这个args就是JS函数参数的包装 可以理解成数组
int64_t timeout = args[0]->IntegerValue();
// libuv的方法 第三个参数代表延迟时间
int err = uv_timer_start(&wrap->handle_, OnTimeout, timeout, 0);
// 设置该函数返回值
args.GetReturnValue().Set(err);
}
可以看到,这里涉及到了libuv,继续深入,看该方法:
/*
handle => 时间模块对象
cb => 延迟回调函数
timeout => 延迟时间
repeat => 区分interval/setTimeout
*/
int uv_timer_start(uv_timer_t* handle,
uv_timer_cb cb,
uint64_t timeout,
uint64_t repeat) {
uint64_t clamped_timeout; if (cb == NULL)
return UV_EINVAL; if (uv__is_active(handle))
uv_timer_stop(handle);
// 当前时间戳加上延迟的时间 也就是回调函数触发的时间戳
clamped_timeout = handle->loop->time + timeout;
if (clamped_timeout < timeout)
clamped_timeout = (uint64_t)-1; // 对象赋值
handle->timer_cb = cb;
handle->timeout = clamped_timeout;
handle->repeat = repeat;
/* start_id is the second index to be compared in uv__timer_cmp() */
handle->start_id = handle->loop->timer_counter++;
// 注意这里,用了insert方法将对应的handle对象插入到了树中
heap_insert(timer_heap(handle->loop),
(struct heap_node*) &handle->heap_node,
timer_less_than);
uv__handle_start(handle); return 0;
}
简单说明一下,首先第一个参数可以直接当成个空对象,在一开始是啥都没有的。
然后是clamped_timeout,在上一节中讲过,libuv内部获取的是一个相对时间,所以这里用当前轮询的时间点加上延迟时间,得到的就是理论上的触发时间点。
而timer_cb就很好理解了,对应的是回调函数。
repeat这个值,如果是setInterval,那么值为interval的间隔时间,setTimeout就是0,表示是否循环触发。
最后将这几个值都挂载到handle上面,通过insert方法插入这一节一开始讲的树上。
至此,一个setTimeout方法所完成的操作已经讲完了。
显然我又错了,这个start并没有触发什么东西,最终只是把一个对象加到一个树结构上,那么又是在哪里触发的延迟调用呢?
答案就在uv_run中,因为偷懒,所以之前没有贴完整代码,在每一轮的事件轮询中,有两个首要操作,如下:
int uv_run(uv_loop_t *loop, uv_run_mode mode) {
// ...略 while (r != && loop->stop_flag == ) {
// 上一节的更新时间
uv_update_time(loop);
// 这一节的内容
uv__run_timers(loop); // ...
}
}
第一个就是上一节讲的更新时间,第二个就涉及到延迟触发了,进入源码看一下:
void uv__run_timers(uv_loop_t* loop) {
struct heap_node* heap_node;
uv_timer_t* handle; // 死循环 保证触发所有应该触发的延迟事件
for (;;) {
// 该方法返回延迟事件树中最小的时间点
heap_node = heap_min(timer_heap(loop));
// 代表没有延迟事件
if (heap_node == NULL)
break;
// 取出handle
handle = container_of(heap_node, uv_timer_t, heap_node);
// 比较handle的时间点与当前的时间点
if (handle->timeout > loop->time)
break;
// 移除当前的handle
uv_timer_stop(handle);
// 如果是interval 需要重新插入一个新的handle到树中
uv_timer_again(handle);
// 触发延迟事件
handle->timer_cb(handle);
}
}
这里就把上面的树与事件轮询链接起来了,每一次轮询,首先触发的就是延迟事件,触发的方式就是去树里面找,有没有比当前时间点小的handle,取出一个,删除并触发。
下面用一个图来总结一下:
完结撒花!
深入出不来nodejs源码-timer模块(C++篇)的更多相关文章
- 深入出不来nodejs源码-timer模块(JS篇)
鸽了好久,最近沉迷游戏,继续写点什么吧,也不知道有没有人看. 其实这个node的源码也不知道该怎么写了,很多模块涉及的东西比较深,JS和C++两头看,中间被工作耽搁回来就一脸懵逼了,所以还是挑一些简单 ...
- 深入出不来nodejs源码-events模块
这一节内容超级简单,纯JS,就当给自己放个假了,V8引擎和node的C++代码看得有点脑阔疼. 学过DOM的应该都知道一个API,叫addeventlistener,即事件绑定.这个东西贯穿了整个JS ...
- 深入出不来nodejs源码-V8引擎初探
原本打算是把node源码看得差不多了再去深入V8的,但是这两者基本上没办法分开讲. 与express是基于node的封装不同,node是基于V8的一个应用,源码内容已经渗透到V8层面,因此这章简述一下 ...
- 深入出不来nodejs源码-编译启动(1)
整整弄了两天,踩了无数的坑,各种奇怪的error,最后终于编译成功了. 网上的教程基本上都过时了,或者是版本不对,都会报一些奇怪的错误,这里总结一下目前可行的流程. node版本:v10.1.0. 首 ...
- 深入出不来nodejs源码-从fs.stat方法来看node架构
node的源码分析还挺多的,不过像我这样愣头完全平铺源码做解析的貌似还没有,所以开个先例,从一个API来了解node的调用链. 首先上一张整体的图,网上翻到的,自己懒得画: 这里的层次结构十分的清晰, ...
- 深入出不来nodejs源码-内置模块引入再探
我发现每次细看源码都能发现我之前写的一些东西是错误的,去改掉吧,又很不协调,不改吧,看着又脑阔疼…… 所以,这一节再探,是对之前一些说法的纠正,另外再缝缝补补一些新的内容. 错误在哪呢?在之前的初探中 ...
- 深入出不来nodejs源码-内置模块引入初探
重新审视了一下上一篇的内容,配合源码发现有些地方说的不太对,或者不太严谨. 主要是关于内置模块引入的问题,当时我是这样描述的: 需要关注的只要那个RegisterBuiltinModules方法,从名 ...
- 深入出不来nodejs源码-流程总览
花了差不多两周时间过了下primer C++5th,完成了<C++从入门到精通>.(手动滑稽) 这两天看了下node源码的一些入口方法,其实还是比较懵逼的,语法倒不是难点,主要是大量的宏造 ...
- JUC源码分析-线程池篇(三)Timer
JUC源码分析-线程池篇(三)Timer Timer 是 java.util 包提供的一个定时任务调度器,在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次. 1. Ti ...
随机推荐
- mac系统下安装Windows(7,8,10都一样的步骤)
1.下载纯净版window10镜像文件(ISO) 注意:必须是纯净版,不是ghost版 2.打开bootcamp软件(位置:launchpad-其他) 注意:硬盘不要分区,若分过区,请合并. ...
- WPF实现可视化控件打印及打印预览
打印预览XAML代码: <controls:WindowEx x:Class="SunCreate.Vipf.Client.UI.MapPrintPreview" xmlns ...
- 【BZOJ3097】 Hash Killer I
BZOJ3097 Hash Killer I Solution 考虑它是自然溢出,相当于就是对\(2^{63}\)取膜 那么就有\(aaaaa...aaa\)(多于64个)和\(baaaa...aaa ...
- Spring Boot log4j实现把日志存入mongodb
准备工作 1.自定义appender的实现 log4j提供的输出器实现自Appender接口,要自定义appender输出到MongoDB,只需要继承AppenderSkeleton类,并实现几个方法 ...
- 集合框架_DAY15
1:集合(掌握) (1)集合的由来 我们需要对多个对象进行存储和获取.可以使用对象数组.但是,如果对象的个数是变化的,对象数组就解决不了了.怎么办呢?java就提供了集合类解决. (2)集合和数组的区 ...
- Firefox火狐 浏览器接口调试工具 JSON 格式化
作为一名“IT界”的淫才,还是主攻Web端的淫才,相信大家经常会联调各种接口! 如今APP猖狂的年代接口联调更为频繁,当然我们常用于Firefox火狐 浏览器,所以这里主要讲Firefox火狐 浏览器 ...
- echarts初探
最近经常看到echarts,觉得很有意思,并且这个库是百度开发的,目前来说使用的也很广泛,包括百度.阿里.腾讯.网易.小米.新浪.华为.联想.美团等一大批一线互联网公司在使用,且github上的sta ...
- PHP:使用Zend对源码加密、Zend Guard安装以及Zend Guard Run-time support missing的解决方法
Zend Guard是目前市面上最成熟的PHP源码加密产品了.刚好需要对自己的产品进行加密,折腾了一晚上,终于搞定,将碰到的问题及解决方法记录下来,方便日后需要,也可以帮助其他人.我使用的是Wamps ...
- webstorm引用ESLint进行静态代码检查
安装 ESLint 基于 Node 平台,所以 Nodejs 是必须安装的,然后通过 npm 安装 ESLint 包,至于全局安装还是作为开发依赖安装,取决于个人. 然后在 WebStorm 中,打开 ...
- Zookeeper--0200--安装与集群搭建、常用命令、客户端工具
看这里,http://www.cnblogs.com/lihaoyang/p/8358153.html 1,先使用可视化客户端软件 ZooInspector 连接上集群中的一个节点,看下zk的结构: ...