📜  Node.js 如何克服 I/O 操作阻塞的问题?

📅  最后修改于: 2022-05-13 01:56:15.520000             🧑  作者: Mango

Node.js 如何克服 I/O 操作阻塞的问题?

Node.js 使用非阻塞 I/O,这种机制允许您让一个执行线程运行您的程序。如果 Node.js 必须使用阻塞 I/O,那么在等待 I/O 完成时您将无法做任何其他事情。下面是当 Node.js 需要使用阻塞 I/O 时发生的示例图像:

阻塞与非阻塞 I/O 操作

Node.js 项目包括一个 JavaScript 引擎、事件循环和 I/O 层。它通常被称为非阻塞 Web 服务器。

注: “I/O”一般指系统磁盘与网络的交互,libuv支持。

为什么阻塞 I/O 操作是个问题?

在 C 和PHP等传统编程语言中,默认情况下所有指令都会被阻止,除非您明确“启用”它来执行异步操作。假设您发出网络请求以读取一些数据,或者您可能想要读取一些文件并希望向用户显示其数据,但是随后该线程的执行被阻止,直到应答响应准备好但要解决这个问题I/O 流阻塞问题 JavaScript 允许您通过使用单线程、回调函数和事件驱动编程以非常简单的方式创建异步和非阻塞代码。

让我们看一个例子来更好地理解这一点!

示例 1:以下示例使用 readFileSync()函数读取文件并演示 Node.js 中的阻塞:

Javascript
// The fs module provides access to 
// interact with the file system.
  
const fs = require('fs');
const data = fs.readFileSync('/sample.txt');
  
/*This line of code will block the main thread, 
  until the file is read.*/
  
console.log(data);
console.log("This is a beautiful message");
console.log("This code is doing some great work")


Javascript
const fs = require('fs');
  
// This piece of code doesn't block the main thread
// but will run after the file is completely read.
  
fs.readFile('/sample.txt', (err, data) => {
    if (err) throw err;
    else {
        // Call-back function
        console.log(data);
    }
});
  
console.log("This is a beautiful message");
console.log("This code is doing some great work")


输出:

This data is from the text file
This is a beautiful message
This code is doing some great work

说明:在上面的例子中,我们看到 Blocking 方法是同步执行的[或者你可以说一行一行]。现在您看到每一行代码都在等待执行前一行以获得结果,这可能会成为一个问题,尤其是对于读取或更新等慢速操作,或者您可以说与 I/O 相关的操作,因为每一行都会阻止执行剩下的代码,我们说它是阻塞代码,因为下一行代码只能在前一行代码完成后执行,因为 Node.js 是如何设计的,结果证明这是一个巨大的问题,导致我们异步执行代码的非阻塞方法意味着代码不是逐行执行,而是使用回调来实现这种非阻塞行为。下面是同一个例子,我们在上面描述了同步行为,但现在我们使用回调函数对其进行了修改以使其异步。

示例 2:以下示例使用 readFile()函数读取文件并演示 Node.js 中的非阻塞

Javascript

const fs = require('fs');
  
// This piece of code doesn't block the main thread
// but will run after the file is completely read.
  
fs.readFile('/sample.txt', (err, data) => {
    if (err) throw err;
    else {
        // Call-back function
        console.log(data);
    }
});
  
console.log("This is a beautiful message");
console.log("This code is doing some great work")

输出:

This is a beautiful message
This code is doing some great work
This data is from the text file

注意:在上面的例子中,我们看到在控制台的非阻塞方法中,实际上是在文件内容之前打印消息。这是因为程序不等待 readFile()函数返回并移动到下一个操作,使其异步。当 readFile()函数返回时,它会打印文本文件的内容。

node.js 是如何解决这个问题的?

您可能听说过这个称为非阻塞 I/O 的概念,以及 Node.js 如何使用它来解决阻塞调用问题并实现超快运行,但非阻塞 I/O 是什么,为什么它会有所帮助?我们稍后会理解这一点,但首先,您需要了解服务器和线程是如何工作的,以及服务器如何处理请求。在进入 node.js 之前,让我们先来概述一下服务器和线程。

服务器只是接受请求,并做一些工作来计算需要发回的响应,例如,当您访问 google.com 时,您向 Google 服务器发送 HTTP 请求,该服务器计算您应该看到的 HTML 响应您的浏览器主页。但在内部,服务器将工作分配给内部的一个或多个线程[一个线程可以认为是一个工作者],在不阻塞主线程的情况下同时处理其他用户的需求和请求。

我们可以拿餐厅做个比喻来探讨一下,这里在一家经常光顾的餐厅里,只有一个服务员,因为它不是很受欢迎,顾客往往会一个接一个地来,服务员会为每一方服务,然后再去下一个。服务员结束了一个聚会,它要么在等待更多的顾客,要么切换到另一个这个例子很好地描述了服务器。

同步与异步

我们刚才提到的餐厅就像一个服务器,有请求的频率,服务员就像一个线程请求就像客户

现在想象一下,如果两个人同时进入餐厅,一个服务员一次只能为一个人服务,但事实并非如此,看看当顾客忙于菜单时他们不需要你的帮助,所以服务员可以移动,或者您可以说在桌子之间切换并同时帮助两者,因为每个人都需要帮助,而无需让某人站立或等待轮到他们。

好吧,当两个朋友同时需要帮助时会发生什么,那么一个人需要等待的时间比只是一个聚会的时间长一点。这里的基本思想是服务员一次可以为多张桌子服务,因为每张桌子都有一些停机时间。

所有这些都适用于服务器和 Node JS!

就像我们需要时间请求处理的表格一样,也有一部分需要积极关注,而不需要积极关注的部分通常称为 CPU 工作,因为它需要我们花费时间的计算机的中央处理单元思考和计算结果。

CPU 的工作需要一个线程来处理它,就像一个需要等待者来处理它的表一样,但是不需要激活注意的部分称为 I/O,因为它正在等待其他东西提供输入或发送输出。

这里是退出部分,就像服务员可以通过改表来节省时间一样,同样的阻塞允许线程通过更改请求来破坏时间,当请求进行 I/O 时,大大增加了 I/O 的有效工作量哦可以!

注意:所以,node.js 中这个问题的解决方案是使用异步非阻塞代码,而 Node.js 为此使用了一个事件循环。 “处理和处理外部事件并将其转换为回调调用的对象”就是事件循环。

非阻塞 I/O 事件循环

当需要数据时,Node.js 会记录一个回调并将操作发送到此事件循环。当数据可用时调用回调。所以基本上,我们将繁重的工作负载卸载到后台运行,然后当工作完成时调用回调函数来处理结果,我们之前保存了,在此期间其余代码仍然可以执行,而当前阻塞的繁重任务正在后台运行。

简而言之:

一切都并行运行,除了您的代码,我们总是尝试传递一个回调函数,一旦我们完成任务并继续处理,该函数将被调用。我们不会等到这一切结束才继续其余的工作。