终于可以填上坑了。

  简单回顾一下之前JS篇内容,每一次setTimeout的调用,会在一个对象中添加一个键值对,键为延迟时间,值为一个链表,将所有该时间对应的事件串起来,图如下:

  而每一个延迟键值对的触发,则是在链表头生成的时候就已经开始了,如下:

  1. function TimersList(msecs, unrefed) {
  2. //...
  3.  
  4. // 来源于C++内置模块
  5. const timer = this._timer = new TimerWrap();
  6. /// ...
  7.  
  8. // 触发
  9. timer.start(msecs);
  10. }

  回顾完毕。

  与JS篇一样,这一节也简单介绍libuv内部的一个数据结构:二叉树。源码来源于:uv/src/heap-inl.h。

  因为二叉树的介绍网上一堆,所以这里只看一下API,首先是节点:

  1. struct heap_node {
  2. struct heap_node* left;
  3. struct heap_node* right;
  4. struct heap_node* parent;
  5. };

  分别代表左右、父节点。

  1. /* A binary min heap. The usual properties hold: the root is the lowest
  2. * element in the set, the height of the tree is at most log2(nodes) and
  3. * it's always a complete binary tree.
  4. *
  5. * The heap function try hard to detect corrupted tree nodes at the cost
  6. * of a minor reduction in performance. Compile with -DNDEBUG to disable.
  7. */
  8. struct heap {
  9. struct heap_node* min;
  10. unsigned int nelts;
  11. };

  这里的注释可以看一下,这个结构体是独立的,min指向当前树的最小值。

  另外还有三个操作方法:

  1. HEAP_EXPORT(void heap_insert(struct heap* heap,
  2. struct heap_node* newnode,
  3. heap_compare_fn less_than));
  4. HEAP_EXPORT(void heap_remove(struct heap* heap,
  5. struct heap_node* node,
  6. heap_compare_fn less_than));
  7. HEAP_EXPORT(void heap_dequeue(struct heap* heap, heap_compare_fn less_than));

  分别代表树的插入、移除,以及将小于指定值的节点移除并重新整理树,实现自己去看,懒得讲。

  进入正题,从JS的触发代码开始看。

  废话不多说,直接进入timer_wrapper.cc看start方法,源码如下:

  1. static void Start(const FunctionCallbackInfo<Value>& args) {
  2. // 不管这3行
  3. TimerWrap* wrap;
  4. ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
  5.  
  6. CHECK(HandleWrap::IsAlive(wrap));
  7. // 这个args就是JS函数参数的包装 可以理解成数组
  8. int64_t timeout = args[0]->IntegerValue();
  9. // libuv的方法 第三个参数代表延迟时间
  10. int err = uv_timer_start(&wrap->handle_, OnTimeout, timeout, 0);
  11. // 设置该函数返回值
  12. args.GetReturnValue().Set(err);
  13. }

  可以看到,这里涉及到了libuv,继续深入,看该方法:

  1. /*
  2. handle => 时间模块对象
  3. cb => 延迟回调函数
  4. timeout => 延迟时间
  5. repeat => 区分interval/setTimeout
  6. */
  7. int uv_timer_start(uv_timer_t* handle,
  8. uv_timer_cb cb,
  9. uint64_t timeout,
  10. uint64_t repeat) {
  11. uint64_t clamped_timeout;
  12.  
  13. if (cb == NULL)
  14. return UV_EINVAL;
  15.  
  16. if (uv__is_active(handle))
  17. uv_timer_stop(handle);
  18. // 当前时间戳加上延迟的时间 也就是回调函数触发的时间戳
  19. clamped_timeout = handle->loop->time + timeout;
  20. if (clamped_timeout < timeout)
  21. clamped_timeout = (uint64_t)-1;
  22.  
  23. // 对象赋值
  24. handle->timer_cb = cb;
  25. handle->timeout = clamped_timeout;
  26. handle->repeat = repeat;
  27. /* start_id is the second index to be compared in uv__timer_cmp() */
  28. handle->start_id = handle->loop->timer_counter++;
  29. // 注意这里,用了insert方法将对应的handle对象插入到了树中
  30. heap_insert(timer_heap(handle->loop),
  31. (struct heap_node*) &handle->heap_node,
  32. timer_less_than);
  33. uv__handle_start(handle);
  34.  
  35. return 0;
  36. }

  简单说明一下,首先第一个参数可以直接当成个空对象,在一开始是啥都没有的。

  然后是clamped_timeout,在上一节中讲过,libuv内部获取的是一个相对时间,所以这里用当前轮询的时间点加上延迟时间,得到的就是理论上的触发时间点。

  而timer_cb就很好理解了,对应的是回调函数。

  repeat这个值,如果是setInterval,那么值为interval的间隔时间,setTimeout就是0,表示是否循环触发。

  最后将这几个值都挂载到handle上面,通过insert方法插入这一节一开始讲的树上。

  至此,一个setTimeout方法所完成的操作已经讲完了。

  显然我又错了,这个start并没有触发什么东西,最终只是把一个对象加到一个树结构上,那么又是在哪里触发的延迟调用呢?

  答案就在uv_run中,因为偷懒,所以之前没有贴完整代码,在每一轮的事件轮询中,有两个首要操作,如下:

  1. int uv_run(uv_loop_t *loop, uv_run_mode mode) {
  2. // ...略
  3.  
  4. while (r != && loop->stop_flag == ) {
  5. // 上一节的更新时间
  6. uv_update_time(loop);
  7. // 这一节的内容
  8. uv__run_timers(loop);
  9.  
  10. // ...
  11. }
  12. }

  第一个就是上一节讲的更新时间,第二个就涉及到延迟触发了,进入源码看一下:

  1. void uv__run_timers(uv_loop_t* loop) {
  2. struct heap_node* heap_node;
  3. uv_timer_t* handle;
  4.  
  5. // 死循环 保证触发所有应该触发的延迟事件
  6. for (;;) {
  7. // 该方法返回延迟事件树中最小的时间点
  8. heap_node = heap_min(timer_heap(loop));
  9. // 代表没有延迟事件
  10. if (heap_node == NULL)
  11. break;
  12. // 取出handle
  13. handle = container_of(heap_node, uv_timer_t, heap_node);
  14. // 比较handle的时间点与当前的时间点
  15. if (handle->timeout > loop->time)
  16. break;
  17. // 移除当前的handle
  18. uv_timer_stop(handle);
  19. // 如果是interval 需要重新插入一个新的handle到树中
  20. uv_timer_again(handle);
  21. // 触发延迟事件
  22. handle->timer_cb(handle);
  23. }
  24. }

  这里就把上面的树与事件轮询链接起来了,每一次轮询,首先触发的就是延迟事件,触发的方式就是去树里面找,有没有比当前时间点小的handle,取出一个,删除并触发。

  下面用一个图来总结一下:

  完结撒花!

深入出不来nodejs源码-timer模块(C++篇)的更多相关文章

  1. 深入出不来nodejs源码-timer模块(JS篇)

    鸽了好久,最近沉迷游戏,继续写点什么吧,也不知道有没有人看. 其实这个node的源码也不知道该怎么写了,很多模块涉及的东西比较深,JS和C++两头看,中间被工作耽搁回来就一脸懵逼了,所以还是挑一些简单 ...

  2. 深入出不来nodejs源码-events模块

    这一节内容超级简单,纯JS,就当给自己放个假了,V8引擎和node的C++代码看得有点脑阔疼. 学过DOM的应该都知道一个API,叫addeventlistener,即事件绑定.这个东西贯穿了整个JS ...

  3. 深入出不来nodejs源码-V8引擎初探

    原本打算是把node源码看得差不多了再去深入V8的,但是这两者基本上没办法分开讲. 与express是基于node的封装不同,node是基于V8的一个应用,源码内容已经渗透到V8层面,因此这章简述一下 ...

  4. 深入出不来nodejs源码-编译启动(1)

    整整弄了两天,踩了无数的坑,各种奇怪的error,最后终于编译成功了. 网上的教程基本上都过时了,或者是版本不对,都会报一些奇怪的错误,这里总结一下目前可行的流程. node版本:v10.1.0. 首 ...

  5. 深入出不来nodejs源码-从fs.stat方法来看node架构

    node的源码分析还挺多的,不过像我这样愣头完全平铺源码做解析的貌似还没有,所以开个先例,从一个API来了解node的调用链. 首先上一张整体的图,网上翻到的,自己懒得画: 这里的层次结构十分的清晰, ...

  6. 深入出不来nodejs源码-内置模块引入再探

    我发现每次细看源码都能发现我之前写的一些东西是错误的,去改掉吧,又很不协调,不改吧,看着又脑阔疼…… 所以,这一节再探,是对之前一些说法的纠正,另外再缝缝补补一些新的内容. 错误在哪呢?在之前的初探中 ...

  7. 深入出不来nodejs源码-内置模块引入初探

    重新审视了一下上一篇的内容,配合源码发现有些地方说的不太对,或者不太严谨. 主要是关于内置模块引入的问题,当时我是这样描述的: 需要关注的只要那个RegisterBuiltinModules方法,从名 ...

  8. 深入出不来nodejs源码-流程总览

    花了差不多两周时间过了下primer C++5th,完成了<C++从入门到精通>.(手动滑稽) 这两天看了下node源码的一些入口方法,其实还是比较懵逼的,语法倒不是难点,主要是大量的宏造 ...

  9. JUC源码分析-线程池篇(三)Timer

    JUC源码分析-线程池篇(三)Timer Timer 是 java.util 包提供的一个定时任务调度器,在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次. 1. Ti ...

随机推荐

  1. mantis邮件设置

     1.cd /var/www/html/mantis     删除 config_inc.php  的$g_enable_email_notification    = OFF;    重启httpd ...

  2. [leet code 100] same tree

    1 题目 Given two binary trees, write a function to check if they are equal or not. Two binary trees ar ...

  3. 程序员、技术领导、管理者各有烦恼,你占了几条?ZZ

    Q1: 作为学生,你学习 SE的烦恼有哪些? http://blog.jobbole.com/101840/

  4. [C# 开发技巧]如何防止程序多次运行

    一.引言 最近发现很多人在论坛中问到如何防止程序被多次运行的问题的,如: http://social.msdn.microsoft.com/Forums/zh-CN/6398fb10-ecc2-4c0 ...

  5. adb错误 - INSTALL_FAILED_NO_MATCHING_ABIS

    #背景 换组啦,去了UC国际浏览器,被拥抱变化了.还在熟悉阶段,尝试了下adb,然后就碰到了这个INSTALL_FAILED_NO_MATCHING_ABIS的坑... #解决方法 INSTALL_F ...

  6. jQuery---ajax---error函数及其参数详解

    使用jquery的ajax方法向服务器发送请求的时候,常常需要使用到error函数进行错误信息的处理,本文详细说明了ajax中error函数和函数中各个参数的用法. 一般error函数返回的参数有三个 ...

  7. ASP.NET自定义错误页并返回正确的500、404状态码

    在项目中,我们常常需要自定义错误页面,但往往返回的状态码都变成了200,对SEO很不友好.我尝试过在百度上寻找解决方案,但找到的资料中说的方法都试过了,发现都是无法返回正确的状态码的. 最后,只好自已 ...

  8. 201621123018《Java程序设计》第8周学习报告

    1. 本周学习总结 以你喜欢的方式(思维导图或其他)归纳总结集合相关内容. 2. 书面作业 1. ArrayList代码分析 1.1 解释ArrayList的contains源代码 contanis方 ...

  9. JS实现网页背景旋转缩放轮播效果

    实现效果:效果预览 css代码: .switch_images { display: inline-block; margin:; padding:; width: 100%; height: 100 ...

  10. vmworkstation安装unbuntu server 网络配置:NAT模式

    之前安装虚拟机测试环境的时候,习惯了使用桥接模式或者仅主机模式:今天偶然发现,其实NAT 模式的网络配置还是挺方便的. 在新建虚拟机的时候,选择网络模式为NAT,虚拟机创建完成之后,在vmworkst ...