前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >nodejs如何利用libuv实现事件循环和异步

nodejs如何利用libuv实现事件循环和异步

原创
作者头像
标子
修改2019-11-26 12:12:16
4.2K0
修改2019-11-26 12:12:16
举报
文章被收录于专栏:前端之心
  1. nodejs是什么?
  2. libuv的工作原理
  3. nodejs的工作原理
  4. nodejs如何使用libuv实现事件循环和异步

1 nodejs是什么?

Nodejs是对js功能的拓展。提供了网络、文件、dns解析、进程线程等功能。

1.1 Nodejs是如何拓展js功能的?

利用v8提供的接口。

1.2 如何在v8新建一个自定义的功能?

代码语言:javascript
复制
// c++里定义
Handle<FunctionTemplate> Test = FunctionTemplate::New(cb);    
global->Set(String::New(“Test"), Test);  

// js里使用  
var test = new Test();

1.3 nodejs是如何实现拓展的

但nodejs不是给每个功能拓展一个对象,而是拓展一个process对象,再通过process.binding拓展js功能。Nodejs定义了一个js对象process,映射到一个c++对象process,底层维护了一个c++模块的链表,js通过调用js层的process.binding,访问到c++的process对象,从而访问c++模块(类似访问js的Object、Date等)。

2 libuv的工作原理

2.1 Libuv是什么?为什么nodejs需要他?

libuv是一个跨平台异步IO库。因为Nodejs是单线程的,作为服务器,他涉及到IO,而IO是会阻塞的,从而影响性能。所以Nodejs把IO操作交给libuv,保证主线程可以继续处理其他事情。Libuv做了什么?Libuv主要是,利用系统提供的事件驱动模块解决网络异步IO,利用线程池解决文件IO。另外还实现了定时器,对进程,线程等使用进行了封装。

1 新建一个uv_loop_t* loop。loop中保存了各个阶段对应的数据结构。

2 执行uv_run函数进入死循环。

3 用户(nodejs)操作loop里的结构,注册事件和回调。

4 libuv在每一轮循环里处理各个阶段。

2.2 libuv的各个阶段(phase)

1 定时器(setTimeout)

2 pending callback

3 idle(自定义)

4 prepare(自定义)

5 poll i/o(网络和文件IO)

6 check(setImmediate)

7 close callback(关闭一个handle)

2.3 libuv的实现

最小堆(定时器)

链表(check、idle等)

线程池(文件io)

操作系统提供的事件驱动模块(网络io)

3 Nodejs的启动流程

1 注册内置c++模块(通过process.binding函数使用内置c++模块)。

2 新建process的c++对象,设置一系列属性(binding),然后挂载到全局。

3 执行bootstrap_node.js,初始化和挂载nextTick,setTimeout等函数,然后加载用户js,编译执行。

4 调用libuv开始事件循环。

3.1 注册内置c++模块

1 每个c++模块由一个node_module结构体管理。

2 用链表的方式把各个模块的node_module连接起来。

3 运行时,js通过process.binding函数从链表中找到对应的模块,从而使用c++模块功能。

3.2 process对象的生成和作用

1 新建一个c++的process对象

代码语言:javascript
复制
// 利用v8新建一个函数  
auto process_template = FunctionTemplate::New(isolate()); 
// 设置函数名  
process_template->SetClassName(FIXED_ONE_BYTE_STRING(isolate(), "process"));  
// 利用函数new一个对象  auto process_object =      process_template->GetFunction()->NewInstance(context()).ToLocalChecked(); 
 // 设置env的一个属性,类型是Object,val是process_object  
set_process_object(process_object);

2 nodejs如何访问global?

通过给global增加一个global属性

代码语言:javascript
复制
 Local<Object> global = env->context()->Global();    
 global->Set("global", global);

3 nodejs如果设置process对象?

编译node_bootstrap.js成c++代码,执行时传入c++的process对象,执行global.process = process; 从js层面来看,是多了一个全局变量process

4 process的Binding函数

process.Binding加载c++模块。实现js使用c++模块功能。const { TCP } = process.binding('tcp_wrap'); const tcp = new TCP();tcp.listen();js里通过process.binding加载一个c++模块的时候,这段js在编译后执行,首先访问js层的process对象,v8知道js的process对象对应是c++的process对象,再通过底层的Binding,就可以使用c++模块的功能了。

3.3 执行bootstrap_node.js

1 挂载全局变量setTimeout,Buffer等,给process对象挂载nexTick函数等初始化工作。

2 执行用户js

3.4 调用libuv开始事件循环。

4 nodejs如何利用libuv实现异步和事件循环?

如何生成任务给事件循环系统消费?

1 setTimeout

2 setImmediate

3 文件io

4 网络io

4.1 setTimeout的实现

setTimeout对应的数据结构
setTimeout对应的数据结构

1 用户调用setTimeout,设置时间是s秒

2 找到s对应的链表L。

3 如果L不存在则新建一个,并在libuv最小堆里新增一个超时节点。

4 往链表L头部插入一个Timeout节点。返回。(最早超时在链表末尾)

5 uv_run执行uv__run_timers判断是否有超时节点。

6 从后往前遍历链表L,如果当前节点没有超时则全部没有超时,设置新的超时时间,否则执行超时回调。

4.2 setImmediate实现

1 nodejs启动的时候注册了check阶段的一个c++层回调是CheckImmediate,该函数再执行js回调processImmediate

2 用户调用setImmediate,生成一个节点插到双向链表。返回。

3 uv_run在check阶段。执行回调。setImmediate和setTimeout的关系这两个其实没什么关系,对应的阶段也不一样。

4.3 文件io

为啥用线程池实现文件操作的异步?

因为文件的异步操作在各操作系统中兼容性不好。libuv线程池默认打开4个,最多打开128个线程。所有线程共享一个任务队列,当有任务的时候,添加到任务队列,线程的工作函数在死循环里不断处理队列里的任务。Libuv初始化的时候,注册了一个异步的io观察者A,用于子线程和主线程间通信的。 io观察者A设置了一个管道文件描述符和回调。子线程完成任务后设置该任务的标记位,然后通过管道通知主线程,主线程在uv_run的poll io阶段会执行观察者A的回调,观察者的回调会判断每个异步任务的状态。然后执行用户的回调。

文件操作的过程

1 打开一个文件,新建一个c++ FSReqWrap对象。设置用户回调。调用FSReqWrap对象的Open,接着调用libuv层uv_fs_open。 uv_fs_open。Libuv生成一个任务放到线程池的任务队列,返回nodejs。Nodejs可以继续做其他事情。

2 线程池处理该任务,线程会阻塞直到任务完成。比如读写文件,dns查询,然后设置任务的完成标记,可以通过管道写端通知主线程。主线程执行c++层回调,再执行js层回调。

4.4 网络io

网络io的实现方案。利用操作系统提供的事件驱动模块。

代码语言:javascript
复制
var http = require('http'); 
http.createServer(function (request, response) { response.end('Hello World\n'); }).listen(9297);

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 nodejs是什么?
    • 1.1 Nodejs是如何拓展js功能的?
      • 1.2 如何在v8新建一个自定义的功能?
        • 1.3 nodejs是如何实现拓展的
        • 2 libuv的工作原理
          • 2.1 Libuv是什么?为什么nodejs需要他?
            • 2.2 libuv的各个阶段(phase)
              • 2.3 libuv的实现
              • 3 Nodejs的启动流程
                • 3.1 注册内置c++模块
                  • 3.2 process对象的生成和作用
                    • 3.3 执行bootstrap_node.js
                      • 3.4 调用libuv开始事件循环。
                      • 4 nodejs如何利用libuv实现异步和事件循环?
                        • 4.1 setTimeout的实现
                          • 4.2 setImmediate实现
                          • 4.3 文件io
                            • 为啥用线程池实现文件操作的异步?
                              • 文件操作的过程
                              • 4.4 网络io
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档