Nodejs libuv运行原理详解

所属分类: 网络编程 / JavaScript 阅读数: 1195
收藏 0 赞 0 分享

前言

这应该是Nodejs的运行原理的第7篇分享,这篇过后,短时间内不会再分享Nodejs的运行原理,会停更一段时间,PS:不是不更,而是会开挖新的坑,最近有在研究RPG Maker MV,区块链,云计算,可能会更新一些相关文章,或者相关教学。

回到正题,异步编程的难点在于请求与响应不是按顺序发生的。以http server 为例,异步编程赋予了server 高并发的品质,而且他可以以很小的资源代价,不断地接受和处理请求。但是快速处理请求不表示快速地返回请求=>高并发不等同于快速反馈。

在Nodejs中,libuv则为异步编程的实现提供了可能。libuv为builtin modules 提供了API,这些API用来支撑请求和数据的返回的异步处理方式。

这一篇分享,我们主要讨论libuv的运行原理,从两个角度出发:

1) libuv的架构

2) 案例,从细节的角度看libuv是如何对待不同I/O请求,按照不同的方式来完成异步请求和数据返回的。

Libuv的架构

从左往右可分为两部分,Network I/O的相关请求,另一部分File I/O,DNS Ops和User Code组成。

上图展示了libuv细节的流程,图中代码很简单,包括2个部分:

1. server.listen()是用来创建TCP server时,通常放在最后一步执行的代码。主要指定服务器工作的端口以及回调函数。

2. fs.open()是用异步的方式打开一个文件。

选择两个示例很简单,因为libuv架构图可视:libuv对 Network I/O和 File I/O采用不同的机制。

上图右半部分,主要分成两个部分:

1. 主线程:主线程也是node启动时执行的现成。node启动时,会完成一系列的初始化动作,启动V8 engine,进入下一个循环。

2. 线程池:线程池的数量可以通过环境变量UV_THREADPOOL_SIZE配置,最大不超过128个,默认为4个。

Network I/O

V8 engine执行从server.listen() 开始,调用builtin module Tcp_wrap 的过程。

在创建TCP链接的过程中,libuv直接参与Tcp_wrap.cc函数中的 TCPWrap::listen() 调用uv_listen()开始到执行uv_io_start()结束。看起来很短暂的过程,其实是类似linux kernel的中断处理机制。

uv_io_start()负载将handle插入到处理的water queue中。这样的好处是请求能够立即得到处理。中断处理机制里面的下半部分与数据处理操作相似,交由主线程去完成处理。

代码逻辑很简单,查看loop中是否包含handle,如果有遍历default loop。

File I/O

这里我们研究一下 File I/O。

同Network I/O一样,我们的应用所依赖的fs模块,后面有一个builtin module Node_file.cc作为支撑。 Node_file.cc包含了各种我们常用的文件操作的接口,例如open, read, write, chmod,chown等。但同时,它们都支持异步模式。 我们通过Node_file.cc中的Open()函数来研究一下具体的实现细节。

如果你用类似source insight之类的代码阅读工具跟踪一下代码调用顺序,会很容易发现对于异步模式,Open()函数会在一系列辅助操作之后,进入函数uv_fs_open(),并且传入了一个FSReqWrap的对象。

FSReqWrap(),从名字可以看得出来,这是一个wrap,且是与FS相关的请求。也就是说,它基于某一个现成的机制来实现与FS相关的请求操作。这个现成的机制就是ReqWrap。好吧,它也是个wrap。乘你还没疯的时候,看一下图6吧。这里完整展示了FSReqWrap类继承关系。

除了FSReqWrap,还有其它Wrap,例如PipeConnectWrap,TCPConnectWrap等等。每个Wrap均为一种请求类型服务。 但是这些wrap,都是node自身的行为,而与libuv相关的是什么呢?上图中表示出了FSReqWrap关键的数据结构 uv_fs_s req__。

让我们把目光回到uv_fs_open()。在调用这个函数时, req__作为其一个重要的参数被传递进去。而在uv_fs_open()内部,req__则被添加到work queue的末尾中去。图3 thread pool中的thread会去领取这些request进行处理。 每个request很像一个粘贴板,它将event loop, work queue,每个请求的处理函数(work()),以及请求结束处理函数(done())绑定在一起。绑定的操作在uv__work_submit()中完成。 例如对于这里的req__,绑定在它身上的work()为uv__fs_work(), done()为uv__fs_done()。

这里有一个比较有意思的问题值得额外看一下。我们的thread pool是在什么时候建立的呢?

答案是:在第一次异步调用uv__work_submit()时。

每个thead的入口函数是 Threadpool.c中的worker()。工作逻辑比较简单,依次取出work queue中的请求,执行绑定在该请求上的work()函数。 前面我们提到的绑定在请求上的done()函数在哪里执行呢?这也是一个比较有意思的操作。libuv通过uv_async_send()通知event loop去执行相应的callback函数,也即我们绑定在request上的done()函数。uv__work_done()用于完成这样的操作。

uv_async_send()与主线程之间通过PIPE通信。

我在这一小节以一个FSReqWrap以及Open()函数为例,描述了libuv处理这种File I/O请求时所涉及的各种操作:

建立thread pool(只建立一次) 在每个请求req__上绑定与其相关的event loop, work queue, work(), done() thread worker()用来处理work queue里面的每个请求,并执行work() 通过uv_async_send()通知event loop执行done()

以上就是关于本次相关的知识点内容,感谢大家对脚本之家的支持。

更多精彩内容其他人还在看

基于jquery封装的一个js分页

基于jquery封装的一个js分页代码,需要的朋友可以参考下。
收藏 0 赞 0 分享

关于js datetime的那点事

关于js datetime的一些使用经验分享,想要了解datetime日期操作的朋友可以参考下。
收藏 0 赞 0 分享

js 关于=+与+=日期函数使用说明(赋值运算符)

js 关于=+与+=日期函数使用说明(赋值运算符),可以看下,就是一些运算符的使用,看哪个更适合你。
收藏 0 赞 0 分享

JS 操作符整理[推荐收藏]

JS 操作符主要包括算术运算符,赋值运算符,比较(关系)运算符,逻辑运算符,串符(连接作用),条件运算符等
收藏 0 赞 0 分享

让html的text输入框只能输入数字和1个小数点(0-59之间可改)

今天有同事需要这个功能,主要是限制用户输入不符合规范的数字与小数点导致不好计算价格问题,特整理了下面的代码,需要的朋友可以参考下。
收藏 0 赞 0 分享

Jquery 获取checkbox的checked问题

这个郁闷了,今天写这个功能的时候发现了问题,上网找了好多资料对照,更加纠结
收藏 0 赞 0 分享

jQuery EasyUI API 中文文档 - DataGrid数据表格

jQuery EasyUI API 中文文档 - DataGrid数据表格使用说明,需要的朋友可以参考下。
收藏 0 赞 0 分享

jQuery EasyUI API 中文文档 - PropertyGrid属性表格

jQuery EasyUI API 中文文档 - PropertyGrid属性表格使用介绍,需要的朋友可以参考下。
收藏 0 赞 0 分享

20款效果非常棒的 jQuery 插件小结分享

这篇文章向大家推荐20款效果非常棒的 jQuery 插件。jQuery 是一个非常优秀的JavaScript库,它简化了 HTML 文档遍历,事件处理,动画以及 Ajax 交互,同时也改变了很多人编写 JavaScript 代码的方式
收藏 0 赞 0 分享

基于Jquery插件开发之图片放大镜效果(仿淘宝)

公司某个网站,需要实现图片预览效果,并能像淘宝一样实现局部分大,使用jquery的朋友可以参考下。
收藏 0 赞 0 分享
查看更多