-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e31fb7d
commit 7225ffd
Showing
7 changed files
with
48 additions
and
213 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
|
||
libuv 的[官方文档](https://docs.libuv.org/en/v1.x/design.html)在阐述他的架构的时候给出来这么一张图,但是仅仅凭着这么一张图并不能让你对其内部机制理解得更透彻。 | ||
|
||
我们知道 node 使用了 V8 引擎,但是在 node 里面 V8 充当的角色更多的是语法解析层面,另外它还充当了 JavaScript 和 c/c++ 的桥梁。但是我们都知道 Node 中一切皆可异步,但这并不是通过 V8 来实现的,充当这个角色的是 libuv。 | ||
|
||
**图 1.3** 中讲解 IO 多路复用时,用户反复询问菜鸟驿站的过程,用技术术语来描述的话,可以称之为**“轮询”**。和 IO 事件类似,我们也可以衍生出其他类型的事件,同样以轮询的方式来进行处理,这基本上就是 libuv 实现异步的核心思想了。 | ||
|
||
在libuv中,有一个 **句柄(handle)** 的概念,每个句柄中存储数据和回调函数之类的信息,句柄在使用前要添加到对应的 **队列(queue)** 或者 **堆(heap)** 中(其实只有定时器句柄使用了[最小堆](https://zh.wikipedia.org/wiki/%E6%9C%80%E5%A4%A7%E2%80%94%E6%9C%80%E5%B0%8F%E5%A0%86) 的数据结构,其他句柄使用队列的数据结构进行存储)。libuv 在进行每一次事件轮询的时候都会从每个类型的句柄中,取出关联的队列或者堆结构进行处理。下面给出事件轮询的流程图: | ||
 | ||
**图1.2.1 默认事件轮询流程图** | ||
|
||
如上图所示,如果我们将 2 3 4 6 7 步去掉,那么就剩下 IO 事件处理的话,基本上就是一个 IO 多路复用的逻辑处理代码了。下面分别介绍 2-7 步的处理阶段处理的逻辑内容: | ||
|
||
- 第 2 步,定时回调,处理 `setTimeout` 和 `setInterval` 的回调。 | ||
- 第 3 步,pending 回调,会执行上次事件轮询中加入的 I/O 回调,有些 *nix 系统会希望 TCP 失败事件延迟触发,这些事件就会在这个阶段被触发。 | ||
- 第 4 步,idle 和 prepare 回调,仅仅在内部使用,有兴趣大家可以参见libuv的测试文件 [test_idle.c](https://github.com/libuv/libuv/blob/v1.x/test/test-idle.c) 。 | ||
- 第 5 步,IO 事件查询,就是前面讲到的检测 IO 多路复用事件的操作;并且触发 IO 事件关联的回调函数(不包括 IO 关闭事件的处理),注意如果 IO 回调的处理事件过长,原来本应该触发的定时回调就会被延迟。Node 可能会在适当的时机在此处堵塞一会儿,等待 IO 事件查询返回数据 。 | ||
- 第 6 步,check 回调,在 Node.js 中被用作运行 `setImmediate` 回调。 | ||
- 第 7 步,句柄关闭回调,用来运行一些类似于 IO 关闭的回调函数。 | ||
|
||
**图1.2.1** 上的所有流程都是在一个线程中执行的,我们也常说 JavaScript 是单线程的。但是 Node 中是否就只有一个线程在工作呢?答案是否定的。libuv 中大体上可以把线程分为两类,一类是事件轮询线程(处理 **图 1.2.1** 所示的流程),另一类是线程池。第一类事件轮询线程是单线程,且和 V8 线程(也就是 JavaScript 代码工作线程)是同一个线程;另外一类是一个线程池,我们常见的文件 IO 其实是在这个线程池中做处理的,此外它还能处理 DNS 解析,也能处理用户自己编写的 node 扩展中的逻辑,如果你想自己编写一个 c++ 扩展来异步处理耗时业务的话,就会用上它(我们将在第 9 章讲 c++ 扩展内容)。 | ||
|
||
> Node 从 10 版本开始引入了 Worker threads 的 API ,可以通过 Node 代码直接创建线程。 | ||
我们这里拿文件 IO 处理举个栗子,来描述这两类线程之前是怎么通信的。libuv 在线程池中处理完一个文件 IO 操作后,会通过文件句柄触发 IO 事件;事件轮询线程读取 IO 事件后,执行回调函数,也就是 **图1.2.1** 中第 5 步操作。下面是我们演示用的函数: | ||
|
||
```javascript | ||
var fs = require('fs'); | ||
|
||
fs.exists(__filename, function (exists) { | ||
console.log(exists); | ||
}); | ||
``` | ||
|
||
**代码 1.2.3 fs.exists 函数示例** | ||
|
||
[fs.exists](https://nodejs.org/dist/latest-v6.x/docs/api/fs.html#fs_fs_exists_path_callback) 是 Node 自带的函数,我们在调用的时候传了两个参数,第一个 `__filename` 是 Node 中的一个全局变量,它的值其实是当前执行文件的所在路径,第二个参数是一个回调函数,回调函数中 `exists` 用来表示当前是否存在,很明显当前这段代码最终打印的结果肯定是 `true`,当然我们这里更关心的是整个流程处理,下面用一副数据流向图来将上面流程总结一下: | ||
|
||
 | ||
|
||
**图 1.2.2 Node 中文件 IO 处理数据流向图** |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.