阻塞与非阻塞的概述

目录

本概述介绍了 Node.js 中 阻塞非阻塞 调用之间的区别。 本概述将涉及事件循环和 libuv,但不需要事先了解这些主题。 假定读者对 JavaScript 语言和 Node.js 回调模式 有基本的了解。

"I/O" 主要是指 libuv 支持的与系统的磁盘和网络的交互。

阻塞

阻塞 是当 Node.js 进程中的其他 JavaScript 的执行必须等到非 JavaScript 操作完成时。 发生这种情况是因为在发生 阻塞 操作时事件循环无法继续运行 JavaScript。

在 Node.js 中,由于 CPU 密集而不是等待非 JavaScript 操作(例如 I/O)而表现出较差性能的 JavaScript 通常不称为 阻塞。 Node.js 标准库中使用 libuv 的同步方法是最常用的 阻塞 操作。 原生模块也可能有 阻塞 方法。

Node.js 标准库中所有的 I/O 方法都提供异步版本,非阻塞,接受回调函数。 一些方法也有对应的 阻塞,其名称以 Sync 结尾。

比较代码

阻塞 方法 同步地 执行,非阻塞 方法 异步地 执行。

以文件系统模块为例,这是一个 同步的 文件读取:

这是一个等效的 异步的 示例:

第一个示例看起来比第二个简单,但第二行的缺点是在读取整个文件之前 阻塞 执行任何其他 JavaScript。 请注意,在同步版本中,如果抛出错误,则需要将其捕获,否则进程将崩溃。 在异步版本中,由作者决定是否应如图所示抛出错误。

让我们稍微扩展一下示例:

这是一个类似但不等效的异步示例:

在上面的第一个示例中,console.log 将在 moreWork() 之前被调用。 在第二个示例中,fs.readFile()非阻塞,因此 JavaScript 可以继续执行,moreWork() 将首先被调用。 无需等待文件读取完成即可运行 moreWork() 的能力是允许更高吞吐量的关键设计选择。

并发和吞吐量

Node.js 中的 JavaScript 执行是单线程的,因此并发是指事件循环在完成其他工作后执行 JavaScript 回调函数的能力。 任何预期以并发方式运行的代码都必须允许事件循环在非 JavaScript 操作(如 I/O)发生时继续运行。

例如,让我们考虑这样一种情况:每个对 Web 服务器的请求都需要 50 毫秒才能完成,而这 50 毫秒中有 45 毫秒是可以异步完成的数据库 I/O。 选择 非阻塞 异步操作可以释放每个请求 45 毫秒的时间来处理其他请求。 这仅仅是选择使用 非阻塞 方法而不是 阻塞 方法在容量上的显着差异。

事件循环不同于许多其他语言中的模型,在这些模型中可以创建额外的线程来处理并发工作。

混合阻塞和非阻塞代码的危险

在处理 I/O 时,应该避免一些模式。 让我们看一个例子:

在上面的示例中,fs.unlinkSync() 很可能在 fs.readFile() 之前运行,这将在实际读取之前删除 file.md。 一个更好的写法,它完全是 非阻塞 并且保证以正确的顺序执行:

上面在 fs.readFile() 的回调中放置了对 fs.unlink()非阻塞 调用,这保证了正确的操作顺序。

其他资源

Node.js 中文网 - 粤ICP备13048890号