阻塞与非阻塞概述
此概述介绍了 Node.JS 中阻塞与非阻塞调用之间的区别, 此概述将引用事件循环和 libuv, 但不需要事先了解这些主题, 假设读者对 JavaScript 语言和 Node.JS 回调模式有基本的了解.
"I/O" 主要指与 http://libuv.org/ 支持的系统的磁盘和网络的交互.
阻塞
阻塞是指在 Node.JS 进程中执行其他 JavaScript 必须等到非 JavaScript 操作完成, 发生这种情况是因为在发生阻塞操作时, 事件循环无法继续运行 JavaScript.
在 Node.JS 中, 由于 CPU 密集而不是等待非 JavaScript 操作而表现出较差性能的 JavaScript, 例如 I/O, 通常不称为阻塞. Node.JS 标准库中使用 libuv 的同步方法是最常用的阻塞操作, 原生模块也可能具有阻塞方法.
Node.JS 标准库中的所有 I/O 方法都提供非阻塞的异步版本, 并接受回调函数, 某些方法还具有对应的阻塞方法, 其名称以 Sync 结尾.
比较代码
阻塞方法同步执行, 非阻塞方法异步执行.
以文件系统模块为例, 这是一个同步读取文件的方法:
- const fs = require('fs');
- const data = fs.readFileSync('/file.md'); // blocks here until file is read
这是一个等效的异步示例:
- const fs = require('fs');
- fs.readFile('/file.md', (err, data) => {
- if (err) throw err;
- });
第一个示例看起来比第二个示例更简单, 但缺点是第二行阻止执行任何其他 JavaScript, 直到读取整个文件, 请注意, 在同步版本中, 如果抛出错误, 则需要捕获它, 否则进程将崩溃, 在异步版本中, 由作者决定是否应该如图所示抛出错误.
让我们稍微扩展一下我们的例子:
- const fs = require('fs');
- const data = fs.readFileSync('/file.md'); // blocks here until file is read
- console.log(data);
- // moreWork(); will run after console.log
这是一个类似但不等同的异步示例:
- const fs = require('fs');
- fs.readFile('/file.md', (err, data) => {
- if (err) throw err;
- console.log(data);
- });
- // moreWork(); will run before console.log
在上面的第一个示例中, 将在 moreWork()之前调用 console.log, 在第二个示例中, fs.readFile()是非阻塞的, 因此 JavaScript 执行可以继续, 并且将首先调用 moreWork(), 在不等待文件读取完成的情况下运行 moreWork()的能力是一个关键的设计选择, 可以提高吞吐量.
并发和吞吐量
Node.JS 中的 JavaScript 执行是单线程的, 因此并发性是指事件循环在完成其他工作后执行 JavaScript 回调函数的能力, 任何预期以并发方式运行的代码都必须允许事件循环继续运行, 因为非 JavaScript 操作 (如 I/O) 正在发生.
作为一个例子, 让我们考虑这样一种情况: 每个 web 服务器请求需要 50ms 才能完成, 50ms 中的 45ms 是可以异步完成的数据库 I/O, 选择非阻塞异步操作可以释放每个请求 45 毫秒来处理其他请求, 仅通过选择使用非阻塞方法而不是阻塞方法, 这是容量的显着差异.
事件循环不同于许多其他语言中的模型, 其中可以创建其他线程来处理并发工作.
混合阻塞和非阻塞代码的危险
处理 I/O 时应该避免一些模式, 我们来看一个例子:
- const fs = require('fs');
- fs.readFile('/file.md', (err, data) => {
- if (err) throw err;
- console.log(data);
- });
- fs.unlinkSync('/file.md');
在上面的例子中, fs.unlinkSync()很可能在 fs.readFile()之前运行, 这会在实际读取之前删除 file.md, 写一个更好的方法是完全无阻塞并保证以正确的顺序执行:
- const fs = require('fs');
- fs.readFile('/file.md', (readFileErr, data) => {
- if (readFileErr) throw readFileErr;
- console.log(data);
- fs.unlink('/file.md', (unlinkErr) => {
- if (unlinkErr) throw unlinkErr;
- });
- });
上面在 fs.readFile()的回调中对 fs.unlink()进行了非阻塞调用, 这保证了正确的操作顺序.
来源: https://segmentfault.com/a/1190000017016894