Node.js 中的线程池

2025年2月27日 | 阅读 5 分钟

Node.js 中的线程是单个进程的执行。它是一个小型的处理器,可以与同一进程中的其他线程并发工作。它位于进程内存中,并包含一个执行指针。它有自己的堆栈,但共享进程的公共堆。

Node.js 使用两种线程:一个作为事件循环的单一托管线程,以及池中的几个其他线程。在 Node.js 的上下文中,它与“工作线程”或用于辅助线程的“线程”是可互换的。

在 Node.js 中,主线程是 Node.js 启动时运行的初始执行线程。它负责运行 JavaScript 代码和管理传入的请求。工作线程是程序的执行的辅助线程,它与主线程并发运行。

Node.js 架构概述

在深入了解实际线程池之前,解释 Node.js 的核心架构至关重要。Node.js 使用事件循环来处理异步任务。通过事件循环,Node.js 可以进行同步调用或对涉及大量文件和数据库的数据进行特定操作、查询或发出请求等。

事件循环在单线程上运行,一次只能执行一项操作。但是,Node.js 应用程序中的许多任务都可以使用 Node.js API 中提供的预构建模块来完成。操作应用程序也可以异步完成,因此事件循环可以启动一个任务,然后继续执行另一个任务,而不必等待第一个任务完成。这就是线程池发挥作用的地方。

什么是线程池?

线程池是一组线程,可用于执行并发操作。在 Node.js 中,线程池用于管理不在单线程事件循环中执行的各种操作。这些文件系统 I/O 操作、加密、DNS 查询和压缩可能非常耗时或同步。

Node.js 中的线程池是什么?

Node.js 使用一个名为 libuv 的库来控制异步 I/O 操作。Libuv 还带有一个内置线程池,Node.js 使用它来有效地执行这些操作。默认情况下,此线程池包含四个线程,但可以设置此值。

每当启动一个任务时,它都需要使用线程池。Node.js 将其发送给一个正在等待处理它的活动线程。之后,任务在主事件循环之外进行处理。任务完成后,返回值被返回给事件循环,然后在其中可以执行任务的回调。

示例:Thread.js

输出

 
The Result is: Processed Task_1 data Processed Task_2 data   

线程池的组成部分

Node.js 中的线程池由以下组件构成

  • 工作线程:这些是池中执行任务的线程。默认情况下,Node.js 创建四个工作线程,但可以使用 UV_THREADPOOL_SIZE 环境变量更改此数量。
  • 任务队列:用于维护作业的队列顺序。工作线程将用于选择任务。
  • 完成队列:作业完成后,它会存储在完成队列中。之后,事件循环处理完成队列并执行相关的回调。

由线程池管理的任务

以下是一些可以使用线程池执行的操作。包括:

  • 文件系统 I/O:大多数操作,例如文件的读写,都可以委托给线程池,因此事件循环可以并发处理其他任务。
  • 加密:一些过程,如哈希和加密,计算成本很高,可以在线程池中并行执行。
  • DNS 解析:一些 DNS 操作,例如 DFS 操作和涉及自定义的操作,由线程池管理。
  • 压缩:如 gzip 压缩之类的操作由线程池处理,以防止事件循环中的 I/O 密集型操作被阻塞。

最佳实践

Node.js 中的线程池提供了一种在不使事件循环停滞的情况下处理阻塞操作的方法,但有几个注意事项。

  • 避免阻塞事件循环:通常,线程池可以执行各种任务。程序员应避免编写导致事件循环阻塞的代码。
  • 监控线程池使用情况:如果线程池变慢。可以使用性能分析工具查看线程池的使用情况,并可能调整其大小。
  • 适当地配置线程池大小:建议将线程池大小设置为 CPU 核心数,并根据应用程序的性能规范进行设置。
  • 注意资源限制:池中运行的每个线程都需要系统内存和 CPU 时间才能执行。定义线程池时,请考虑服务器环境的限制。

高级用例

Node.js 中线程池的一些主要用例如下:

  • 调度 Node 中的并发:还应该注意的是,Node.js 的可塑性很强,可以很好地适应许多其他复杂的用途。
  • 数据库操作:尽管当前上下文包含 Node.js 中的许多数据库驱动程序,但它们有许多异步 API 方法。因此,通过在线程池上运行它们,一些操作可以得到进一步优化。
  • 数据处理:通过使用线程池可以轻松处理大型数据集。
  • 与原生模块集成:如果我们的应用程序包含用 C 或 C++ 开发的原生模块,这些模块可以利用线程池执行一系列高性能任务。

结论

总之,Node.js 通过利用同步的单线程模型执行轻量级操作,并使用单独的线程执行阻塞操作来有效地执行任务。此线程池在 libuv 库中可用,并用于各种操作,包括文件系统 I/O、加密、DNS 解析和数据压缩。这种设计使 Node.js 能够同时处理更多请求,因为阻塞任务被重定向到线程池。因此,有必要将查询线程池调整到可用的服务器容量,并持续监控线程池以避免拥塞。这种架构使 Node.js 非常适合可能存在高并发且应用程序需要高度响应性的应用程序。