Skip to content

Commit

Permalink
libuv 教程重写
Browse files Browse the repository at this point in the history
  • Loading branch information
yunnysunny committed Feb 8, 2023
1 parent e31fb7d commit 7225ffd
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 213 deletions.
10 changes: 0 additions & 10 deletions docker/etc/apt/sources.list

This file was deleted.

11 changes: 0 additions & 11 deletions no_used/Dockerfile

This file was deleted.

41 changes: 41 additions & 0 deletions no_used/libuv.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

libuv 的[官方文档](https://docs.libuv.org/en/v1.x/design.html)在阐述他的架构的时候给出来这么一张图![一张图](https://docs.libuv.org/en/v1.x/_images/architecture.png),但是仅仅凭着这么一张图并不能让你对其内部机制理解得更透彻。

我们知道 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 在进行每一次事件轮询的时候都会从每个类型的句柄中,取出关联的队列或者堆结构进行处理。下面给出事件轮询的流程图:
![默认事件轮询流程图](images/uv_run.png)
**图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`,当然我们这里更关心的是整个流程处理,下面用一副数据流向图来将上面流程总结一下:

![](images/fs_io_flow.png)

**图 1.2.2 Node 中文件 IO 处理数据流向图**
155 changes: 0 additions & 155 deletions no_used/native_build_env_set.md

This file was deleted.

1 change: 0 additions & 1 deletion text/00_preface.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,3 @@ nodejs台湾协作电子书(源码参见[github地址](https://github.com/node
* 如果之前接触过 javascript ,但是没有接触过 Node.js ,可以从第3章开始阅读,第3章讲述了一些 Node.js 的基础 API。
* 如果之前接触过 Node.js ,但是没有用过数据库操作,可以阅读第5章,第5章讲述了 Node 中操作 redis 、mongodb 的 API 如何使用;如果没有使用 express ,可以阅读第6章和第7章,这两章讲述了如何利用 express 这个 HTTP 编程框架。
* 最后介绍一下一些独立章节,第4章讲述了 npm 命令的使用教程;第8章讲述了如何使用单元测试框架mocha;第9章讲述了一些线上环境的最佳实践,包括配置文件、进程管理、 docker 等内容;第10章讲述如何编写 c++ 扩展。

Loading

0 comments on commit 7225ffd

Please sign in to comment.