概述

Node.js使用单个线程执行 JavaScript 代码。因此,一次只能执行一行。但是,Node js 可以是多线程的,因为它通过 libuv 模块在 js 中包含隐藏线程,该模块处理网络请求和从光盘读取文件等任务。 Node.js引入了工作线程模块,它允许我们同时启动和运行多个作业。这些工作线程将 CPU 密集型任务分配给大量工作线程以进行优化。

介绍

线程就像一个小进程:它们有自己的指令指针,一次可以执行一个 Javascript 任务。线程驻留在进程的内存中,它们没有内存。线程的执行与进程的执行类似。

默认情况下,Javascript 是单线程的,这意味着只有一个线程可用于执行所有操作。这是 javascript 的主要缺点,它导致了服务器端异步 I/O 的实现,这将减少多线程环境中线程之间的竞争。

现在让我们看看 javascript 是如何演变的。Javascript 引入了一个新概念来防止我们面临的限制,即 Web Workers。Web Worker(js 中的线程)主要用于执行 CPU 密集型 Javascript 任务。它们可以执行长时间运行的任务,而不会影响或阻塞主执行线程。

使用 Constructor,我们可以创建一个 worker 对象,它将运行一个 js 文件。此 js 文件包含用于运行工作线程的代码;这些工作线程在与当前窗口不同的其他全局上下文中运行。但是,Node.js内置的异步 I/O 操作比 Workers 更有效。Web 工作者对 I/O 密集型工作没有多大帮助。

Node JS 运行环境

Node js 与 JavaScript 相关,因为它提供了一个允许在浏览器之外执行 JavaScript 代码的环境。此外,Node js 是开源软件,于 2009 年在 Ryan Dahl 的 JSConf 上向公众发布,它迅速成为构建服务器和物联网相关内容的最流行工具。

Node.js运行时环境由几个部分组成。您必须了解 Google Chrome 的 V8 引擎,该引擎负责执行您编写的 javascript 代码。除了 V8 引擎之外,运行时环境还包含一个名为 Libuv 的库。Libuv 负责处理所有异步 I/O 操作。简而言之,Node js 是 V8 引擎,一些库(如 Libuv)管理 I/O 操作。

在操作系统的上下文中,这些与 I/O 相关的操作也称为繁重作业。Libuv 处理文件和文件夹管理、TCP/UDP 事务、压缩、加密等任务。虽然这些任务中的大多数都设计为异步的,但也有少数是同步的,如果处理不当,可能会导致我们的应用程序停止。这就是 Libuv 包含线程池的原因。

Node js 运行时支持异步执行。因此,它不会等待任务完成,而是将它们发送到特定线程进行执行。然后,它开始处理下一个请求。因此,与其他服务器相比,Node js 服务器具有很强的可扩展性。Node js 也不缓冲数据,而是以块的形式处理整个数据。这大大提高了速度。

另一个优点是 Node js 与包管理器 NPM 捆绑在一起。NPM 或 Node 包管理器包含开发应用程序可能需要的所有 Node 模块。Node js 运行时有多种用途。它被广泛使用的事实引起了对其性能的担忧。这就把我们带到了线程池的原因和内容。

您必须了解主线程和事件循环。许多开发人员不知道我们可以将多线程功能添加到我们的 Node 程序中。但是,即使 Node js 支持异步活动,某些同步作业也会阻塞主线程,直到它们完成。在完成之前,仍有某些同步作业会阻塞主线程。libuv 为一些同步进程提供了一个额外的线程池,它可以从中分配 CPU 工作负载。

Node JS 运行环境的缺点

  • CPU 密集型程序:无法有效处理。Node.js 不适合,因为它是基于事件的单线程,因此效率不足以处理 CPU 密集型项目。发请求(例如生成音乐、视频或更改图像)不受Node.js管理。
  • 成熟度不足。使用 Node 的开发人员可以访问社区之前创建的众多第三方模块。然而,整个生态系统仍处于起步阶段。由于缺陷和版本不一致,不适合维护人员。
  • 简单:.与 Node.js 相比,PHP 非常简单。PHP 的复杂性仅足以允许创建简单的应用程序。
  • 不需要客户端应用:。最好的结果来自简单地以 HTML 形式发送数据。PHP 就是为了实现这一点。在这种特殊情况下,PHP 比 Node.js 更有优势。
  • 编码速度:.大多数开发人员认为 Node js 在开发 Web 应用程序方面速度更快。然而,当涉及到快速组合一个项目时,PHP无疑是最好的选择。

线程池

Node js 有一些依赖项,它们提供了一些特定的功能。V8、llhttp、libuv、c-ares 和 OpenSSL 都在其中。libuv 库是用 C 语言编写的库,用于抽象和处理异步、非阻塞 I/O 操作。
这些操作包括:

  • 异步文件操作
  • 异步 DNS 解析
  • 子进程
  • 信号处理
  • 命名管道
  • 定时器
  • 异步 TCP 和 UDP 套接字
  • 线程池

Libuv 库负责为 Node.js 提供多线程,或者能够在 Node js 进程中创建线程池以进行同步活动。线程池由四个线程组成,这些线程处理不应由主线程处理的繁重操作。而且,通过这种配置,这些作业不会妨碍我们的应用程序。如果主线程用于处理所有这些操作,则应用程序可能会停止。

线程池由以下 API 使用:

  • dns.lookup()
  • 所有同步 zlib API
  • 除 fs 之外的所有同步 fs API。FSWatcher()
  • 异步加密 API

您可以将上述列表进一步分类为 CPU 密集型和 I/O 密集型操作。因此,我们可以说 libuv 是 Node js 应用程序具有高可扩展性的原因之一。如果我们的 Node js 应用程序只有事件循环,那么 CPU 密集型和 I/O 密集型操作将导致应用程序频繁停止,因为它们总是会阻塞主线程。

如果我们在事件循环中执行文件压缩,那么它会导致我们的程序挣扎到死。为了解决这个问题,libuv 将简单地启动一个新线程。异步访问文件系统时,也需要一个新线程。这是因为这种 I/O 密集型活动会使主线程变慢并阻塞。另一方面,同步文件系统通常在主线程上处理。

这个 libuv 包允许我们将线程数从 4 个增加到 1024 个。这是因为,每当在四个线程中的任何一个线程中执行其中一个 API 或进程花费的时间比预期的要长时,其他线程的性能都会受到影响。Libuv 启动一个由四个线程组成的线程池,它将同步活动卸载到该线程池中。通过这样做,Libuv 确保同步任务不会不必要地阻止我们应用程序的执行。

您可以利用此配置设置来提高应用程序的性能。您可以通过访问和修改 UV_THREADPOOL_SIZE Node 变量来实现多个线程的更改。

Libuv 的文件 I/O

libuv 使用全局线程池来实现文件 I/O,允许所有循环对它进行排队。它允许以抽象的异步方法使用磁盘。为了启用类似于异步的行为,它将复杂的操作分解为更简单的操作。

例:在大多数情况下,如果软件要求将缓冲区写入指定文件,则 I/O 将停止,直到操作完成或成功而没有 libuv。但是,libuv 通过引入事件通知以异步方式覆盖了这一点,该事件通知将在操作完成时通知操作的成功或失败;在此之前,其他 I/O 操作可以毫无问题地执行。

注意:libuv 不保证线程安全。(少数例外)

与事件循环相比,文件 I/O 采用与平台无关的技术。
文件 I/O 处理三种不同类型的异步光盘 API:

  • Posix AIO(支持 BSD、Linux、Solaris、Mac OS X、AIX 等)
  • Linux AIO(内核支持)
  • Windows 重叠 I/O

其他 Libuv 特性(高分辨率时钟、信号处理)

​​​​​​​

  • 异步 DNS 解析:名称解析由 node:dns 模块启用。例如,您可以使用它来查找主机名和 IP 地址。尽管它带有域名系统 (DNS) 一词,但查找并不总是遵循 DNS 协议。为了解析名称,dns.lookup() 利用了操作系统的资源。它可能根本不需要任何网络交互。使用 dns.lookup() 执行名称解析的方式与同一系统上的其他应用相同。

    
      
    const dns = require('node:dns');
    dns.lookup('example.org', (err, address, family) => {
      console.log('address: %j family: IPv%s', address, family);
    });
    // address: "93.184.216.34" family: IPv4
    
  • 异步文件和文件系统操作:Node 使用符合 POSIX 标准的函数的简单包装器来实现文件 I/O。可以使用以下语法导入节点文件系统 (fs) 模块:

    
      
    var fs = require("fs")
    

    同步和异步变体都可用于 fs 模块中的每个方法。异步方法接受错误作为其第一个参数,并将完成的回调函数作为其最终参数。异步方法比同步方法更可取,因为前者在运行时从不阻塞程序,而后者则阻塞程序。

  • ANSI 转义码控制 TTY:tty。ReadStream 和 tty。WriteStream 类由 node:tty 模块提供。在大多数情况下,不需要或不可行地直接使用此模块。但是,可以使用以下命令访问它:

    
      
    const tty = require('node:tty');
    

    默认情况下,当 Process.stdin 确定 Process.stdin 在附加了文本终端 (TTY) 的情况下运行时,Node.js 将初始化为 tty 的实例。默认情况下,ReadStream 以及 process.stdout 和 process.stderr 将是 tty 的实例。WriteStream。

  • 具有套接字共享的 IPC,使用 Unix 域套接字或命名管道 (Windows):在 Windows 上,node:net 模块通过命名管道启用 IPC,在其他操作系统上使用 Unix 域套接字。

  • 子进程节点:子进程模块允许你生成类似于 popen(3) 但不完全相同的子进程。子 process.spawn() 函数主要负责此功能。

  • 线程池:所有繁重的工作都由 Libuv 委托给一个工作线程池。文件 I/O 和 DNS 查找由线程池处理。但是,所有回调都在主线程上执行。与 Node 10.5 一样,程序员还可以使用工作线程并发运行 Javascript。默认情况下,Libuv 使用四个线程,但是,可以使用 UV THREADPOOL SIZE 环境变量进行修改。

  • 信号处理:信号是一种基于POSIX(便携式操作系统接口)的互通技术。当 Node 接收到信号事件时,这些事件将被传输。发出通知以告知收件人已发生事件。信号名称将由相应的信号处理程序接收。信号名称是事件名称的大写。,例如,SIGTERM 表示 SIGTERM 信号语法:

    
      
    process.on(signalName, callback);
    
  • 高分辨率时钟:使用 POSIX 计时器 API 时,高分辨率计时器系统允许更精确地从计时器事件中唤醒用户空间应用程序。如果 Date.now 不够精确,则可以使用 process.hrtime。

  • 线程和同步基元:npm 包 async-mutex 实现同步基元,使你能够确定性地控制异步事件。

  • 异步 TCP 和 UDP 套接字:UDP 数据报套接字通过节点模块实现。节点模块提供了一个异步网络 API,用于构建 TCP 或 IPC 服务器和客户端(net.createServer() 和 net.createConnection())。

结论

  • 线程驻留在进程的内存中,它们没有内存。
  • Web Worker(js 中的线程)主要用于执行 CPU 密集型 Javascript 任务。
  • Web Worker 可以执行长时间运行的任务,而不会影响或阻塞主执行线程。
  • Node.js运行时环境由 Chrome 的 V8 引擎和 libuv 库组成。
  • libuv 为一些同步进程提供了一个额外的线程池,它可以从中分配 CPU 工作负载。
  • libuv 库是用 C 语言编写的库,用于抽象和处理异步、非阻塞 I/O 操作。
  • Libuv 库负责为 Node.js 提供多线程,或者能够在 Node js 进程中创建线程池以进行同步活动。
  • 这个 libuv 包允许我们将线程数从 4 个增加到 1024 个。
  • libuv 的其他功能包括异步 DNS 解析、异步文件和文件系统操作、带套接字共享的 IPC、使用 Unix 域套接字或命名管道 (Windows)、信号处理、高分辨率时钟以及异步 TCP 和 UDP 套接字。
Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐