好久没写东西了,过了一段咸鱼生活,无意中想起了脉脉上面一句话: 始终保持自己的竞争力。所以,继续开写!

  一般的JavaScript源码看的已经没啥意思了,我也不会写什么xx入门新手教程,最终决定还是啃原来的硬骨头,从外层libuv => node => v8一步步实现原有的目标吧。

  libuv核心还是事件轮询,前几天从头到尾看了一遍官网的文档,对此有了一些更深的理解。

  (虽然现在开发用的mac,但是为了衔接前面的文章,所以代码仍旧以windows系统为基础,反正差别也不大)

  首先看一眼官网给的图:

  理论上轮询都是一个无尽循环,所以不用在意loop alive问题。

  上图中,udpate loop time、Run due timers两块内容我已经在别的博客中讲解过,这里就懒得发传送门了。

  有两个简单的概念需要稍微提一下,libuv中有两个抽象概念贯穿整个框架:handle、request。其中handle生命周期较长,且有自己回调方法的一个事务,比如说TCP的handle会处理每一个TCP连接,并触发connection事件。request属于handle中一个生命周期短,且简单的行为,比如向文件进行读、写等等。

  这一篇主要看一下接下来剩余的部分,由于性质不太一样,所以并不会按顺序依次分析,而是从易到难,且源码会做大量简化,有兴趣的人可以自己去看。

  事件轮询方法源码精炼如下:

int uv_run(uv_loop_t *loop, uv_run_mode mode) {
// ... while (r != && loop->stop_flag == ) {
// update loop time
uv_update_time(loop);
// run due timers
uv__run_timers(loop);
// call pending callbacks
ran_pending = uv_process_reqs(loop);
// run idle handles
uv_idle_invoke(loop);
// run prepare handles
uv_prepare_invoke(loop);
// poll的阻塞时间处理
timeout = ;
if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
timeout = uv_backend_timeout(loop);
// poll for I/O
if (pGetQueuedCompletionStatusEx)
uv__poll(loop, timeout);
else
uv__poll_wine(loop, timeout); // run check handles
uv_check_invoke(loop);
// call close callbacks
uv_process_endgames(loop);
}
// ...
return r;
}

Call close callbacks

  这类回调比较特殊,官网是这么解释的: Close callbacks are called. If a handle was closed by calling uv_close() it will get the close callback called.

  简单来讲,就是仅在为了关闭一个handle,调用uv_close方法中所带的callback会被认为是一个close callbacks。在使用node的时候,所有的操作(比如fs.readFile)不可主动取消,所以轮询中这一步在JS层面是感知不到的。

  作用上相当于vue钩子函数中的destroy,由于触发是在轮询的最后一步,适合做一些收尾的工作,比如关闭文件描述符等等。

  源码中体现如下,首先是uv_close:

void uv_close(uv_handle_t* handle, uv_close_cb cb) {
// 很多代码... case UV_PREPARE:
uv_prepare_stop((uv_prepare_t*)handle);
uv__handle_closing(handle);
uv_want_endgame(loop, handle);
return;
}

  uv_close方法除了做关闭handle的本职工作,在最后都会调用一个uv_want_endgame方法收尾,这个方法是一个静态方法。

INLINE static void uv_want_endgame(uv_loop_t* loop, uv_handle_t* handle) {
if (!(handle->flags & UV_HANDLE_ENDGAME_QUEUED)) {
handle->flags |= UV_HANDLE_ENDGAME_QUEUED; handle->endgame_next = loop->endgame_handles;
loop->endgame_handles = handle;
}
}

  内容十分简单,将handle插入到endgame_handles这个链表的表头。

  最后,只需要看一眼uv_process_endgames即可。

INLINE static void uv_process_endgames(uv_loop_t* loop) {
uv_handle_t* handle; while (loop->endgame_handles) {
handle = loop->endgame_handles;
loop->endgame_handles = handle->endgame_next; handle->flags &= ~UV_HANDLE_ENDGAME_QUEUED; switch (handle->type) {
case UV_TCP:
uv_tcp_endgame(loop, (uv_tcp_t*) handle);
break;
// ...
}
}
}

  也很简洁明了,不停的取出endgame_handles链表中的handle,依次调用不同的callbacks即可。

Run idle hanldes、Run prepare handles、Run check handles

  这三个虽然名字不一样,但是主要作用类似,只是在调用顺序上有所不同。

  由于Poll for I/O是一个比较特殊的操作,所以这里提供prepare、check两个钩子函数可以在这个事务前后进行一些别的调用,大可以用vue的钩子函数created、mounted来帮助理解。

  idle除去调用较早,也影响poll for I/O这个操作的阻塞时间timeout,官网原文: If there are any idle handles active, the timeout is 0.正常情况下事件轮询会根据情况计算一个阻塞时间timout来决定poll for I/O操作的时间。

  这里用一个C++例子来证明调用顺序,忽略上面的宏,直接看main函数,特别简单!!!

#include <iostream>
#include "uv.h"
using namespace std; void idle_callback(uv_idle_t* idle);
void prepare_callback(uv_prepare_t* prepare);
void check_callback(uv_check_t* check); #define RUN_HANDLE(type) \
do { \
uv_##type##_t type; \
uv_##type##_init(loop, &type); \
uv_##type##_start(&type, type##_callback); \
} while() #define CALLBACK(type) \
do { \
cout << "Run " << #type << " handles" << endl; \
uv_##type##_stop(type); \
} while() #define OPEN(PATH, callback) \
do { \
uv_fs_t req; \
uv_fs_open(loop, &req, PATH, O_RDONLY, , callback); \
uv_fs_req_cleanup(&req); \
} while() void idle_callback(uv_idle_t* idle) { CALLBACK(idle); }
void prepare_callback(uv_prepare_t* prepare) { CALLBACK(prepare); }
void check_callback(uv_check_t* check) { CALLBACK(check); }
void on_open(uv_fs_t* req) { cout << "poll for I/O" << endl; } int main(int argc, const char * argv[]) {
auto loop = uv_default_loop(); RUN_HANDLE(check);
RUN_HANDLE(prepare);
RUN_HANDLE(idle); OPEN("/Users/feilongpang/workspace/i.js", on_open); uv_run(loop, UV_RUN_DEFAULT);
uv_loop_close(loop);
return ;
}

  执行的时候还发现了一个问题,如果不提供一个I/O操作,Run check handles那一步是会直接跳过,所以手动加了一个open操作。

  可以看到,我特意调整了callback的添加顺序,但是输出依然是:

  所以,代码确实是按照官网示例所给的图顺序来执行。

  剩下两个poll for I/O、pending callbacks留到下一篇讲吧。

浅析libuv源码-node事件轮询解析(1)的更多相关文章

  1. 浅析libuv源码-node事件轮询解析(3)

    好像博客有观众,那每一篇都画个图吧! 本节简图如下. 上一篇其实啥也没讲,不过node本身就是这么复杂,走流程就要走全套.就像曾经看webpack源码,读了300行代码最后就为了取package.js ...

  2. 浅析libuv源码-node事件轮询解析(2)

    上一篇讲了轮询的边角料,这篇进入正题.(竟然真有人看我博客,上两个图给你们整理下思路) 这是轮询总流程图. 下图为本节内容简图. Poll for I/O The loop blocks for I/ ...

  3. 浅析libuv源码-node事件轮询解析(4)

    这篇应该能结,简图如下. 上一篇讲到了uv__work_submit方法,接着写了. void uv__work_submit(uv_loop_t* loop, struct uv__work* w, ...

  4. 浅析libuv源码-编译启动

    面试的间隙回头复习了一下node,感觉node就像一个胶带,把V8和libuv粘在了一起. V8毫无疑问,负责解析执行JavaScript,相当于语言层面的桥梁:而libuv则是负责操作系统底层功能的 ...

  5. 浅析libuv源码-获取精确时间

    在Timer模块中有提到,libuv控制着延迟事件的触发,那么必须想办法精确控制时间. 如果是JS,获取当前时间可以直接通过Date.now()得到一个时间戳,然后将两段时间戳相减得到时间差.一般情况 ...

  6. node.js事件轮询(1)

    事件轮询(引用) 事件轮询是node的核心内容.一个系统(或者说一个程序)中必须至少包含一个大的循环结构(我称之为"泵"),它是维持系统持续运行的前提.nodejs中一样包含这样的 ...

  7. 理解Node.js的事件轮询

    前言 总括 : 原文地址:理解Node.js的事件轮询 Node小应用:Node-sample 智者阅读群书,亦阅历人生 正文 Node.js的两个基本概念 Node.js的第一个基本概念就是I/O操 ...

  8. 对Node.JS的事件轮询(Event Loop)的理解

    title: Node.JS的事件轮询(event loop)的理解 categories: 理解 tags: Node JS 机制 当我们知道I/O操作和创建新线程的开销是巨大的! 网站延迟的开销 ...

  9. Node.js的异步IO和事件轮询

     想象一下,以前我们在写程序时, 如果程序在I/O上阻塞了,当有更多请求过来时,服务器会怎么处理呢?在这种情景中通常会用多线程的方式.一种常见的实现是给每个连接分配一个线程,并为那些连接设置一个线程池 ...

随机推荐

  1. map、filter、reduce函数的使用

    1.filter() 作用:过滤 // 1.筛选出大于30的数. const array = [10, 20, 30, 40, 50, 60, 70, 80] // 普通写法 // let newar ...

  2. authenticating with the app store 一直卡住--问题记录

    参考链接:https://blog.csdn.net/csdn2314/article/details/90021367 authenticating with the app store 一直卡住最 ...

  3. 详解Vue响应式原理

    摘要: 搞懂Vue响应式原理! 作者:浪里行舟 原文:深入浅出Vue响应式原理 Fundebug经授权转载,版权归原作者所有. 前言 Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是 ...

  4. poi操作Word创建超链接

    项目引入poi: <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</ ...

  5. 9.为什么要进行系统拆分?如何进行系统拆分?拆分后不用 dubbo 可以吗?

    作者:中华石杉 面试题 为什么要进行系统拆分?如何进行系统拆分?拆分后不用 dubbo 可以吗? 面试官心理分析 从这个问题开始就进行分布式系统环节了,现在出去面试分布式都成标配了,没有哪个公司不问问 ...

  6. Hadoop 从节点的 NodeManager 无法启动

    一.问题描述 日志文件信息如下: -- ::, INFO nodemanager.NodeManager (LogAdapter.java:info()) - registered UNIX sign ...

  7. CentOS 7下简答搭建DNS服务器

    一.DNS相关介绍 DNS(Domain Name System)域名系统协议,作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用挨个记住IP地址.通过主机名,最终得到 ...

  8. linux(05) 编译安装py3

    一.编译安装python3 https://www.cnblogs.com/pyyu/p/9015317.html 1.下载python3的源码 cd /opt yum install wget -y ...

  9. 201871010101-陈来弟《面向对象程序设计(Java)》第十二周学习总结

    201871010101-陈来弟<面向对象程序设计(Java)>第十二周学习总结 实验十   集合与GUI初步 实验时间 2019-11-14 第一部分   理论部分 1.(1) 用户界面 ...

  10. python27期day13:闭包、装饰器初始、标准版装饰器、作业题

    1.闭包: 保护数据安全.保护数据干净性. 2.闭包的定义:在嵌套函数内.使用非全局变量(且不使用本层变量) 将嵌套函数返回 闭包的目的:要接受被装饰的函数和被装饰函数需要的参数3.闭包举例子: de ...