为什么 Node.js 是单线程语言?

2025年2月15日 | 阅读 4 分钟

Node.js 是单线程和多线程的,但更准确地说,它是单线程的。它在单个线程上运行给定的 JavaScript 代码。它在主线程上只处理一个任务。然而,它也有一些多线程功能。它提供了工作线程(worker threads),这对开发者来说是一个很好的优势,因为他们可以创建额外的线程来处理多个任务,这是并行处理的一部分。Node.js 使用一个名为 "libuv" 的库,该库包含一个线程池,允许在后台运行多个任务,这样它们就不会阻塞主线程。

在 Node.js 中,有两种类型的线程:主线程和后台线程。主线程会运行主代码并处理事件和回调。与此同时,其他任务在后台运行。这使得 Node.js 成为一个具有多线程优势的单线程环境。

为了理解 Node.js 的单线程特性,我们必须了解事件驱动架构和非阻塞异步输入输出操作。

事件驱动架构

让我们以一家只有一名服务员的餐厅为例。服务员有很多任务,比如从顾客那里点餐、下单、打扫桌子和为多个桌子服务。在这里,服务员首先向所有顾客点餐,然后他去下单。在餐准备好之前,他会打扫桌子;打扫完之后,他会检查餐是否准备好了。如果餐准备好了,他会一张桌子一张桌子地为顾客服务。服务员在这里不停地工作,没有休息;他能同时高效地处理许多任务,但一次只能做一件事情。

Node.js 的工作方式就像餐厅里的服务员一样。

主线程充当服务员,后台线程充当厨房工作人员,它们执行 I/O 操作,就像工作人员制作食物一样。铃声响了就像回调一样,表示任务已完成。

事件驱动架构是根据软件识别的事件工作的程序流程。用户操作和传感器输出是一些事件的例子。该架构的关键组件是事件循环、事件发射器和事件处理器。事件循环管理代码的执行。它收集所有处理事件并将队列中的子任务执行。事件发射器使应用程序的各个部分发出事件。事件处理器将处理事件。

这种架构以其异步处理、非阻塞特性和高效的资源利用而闻名。任务是异步处理的,主线程不需要等待其他任务完成。主线程将处理其他事件,直到当前任务完成。它非常有效地利用系统资源。

异步编程

它允许 Node.js 独立执行任务。这对于处理需要较长时间才能完成的任务很有用;一些任务是输入输出操作、网络请求和数据库查询,因此这些任务必须在不停止主线程的情况下完成。回调、Promise、async 和 await 在异步编程中起着至关重要的作用。回调是将函数作为参数传递给另一个函数的函数。异步操作完成后,它将执行。Promise 是表示操作状态的对象,无论它是成功、失败还是待定。Async 和 await 是在使用 Promise 时使用的语法,可以让异步代码看起来像同步代码。

事件循环

当 Node.js 接收一个任务时,它会将该任务放入事件队列。该任务会等待队列中的其他任务被处理。事件循环将遍历事件队列。当从事件队列中取出一个特定任务时,它将进入执行阶段,在那里进行处理和执行。对于某些输入输出操作,线程将在后台运行,并且与任务关联了回调。每当任务完成时,主任务就会被放入队列,这个任务也会经历上述过程才能被处理。

Node.js 处理简单任务的流程。

  • 首先,Node.js 启动 V8 引擎来设置环境。
  • 如果存在包含打开文件并读取其中数据的 JavaScript 代码。
  • 之后,它会处理输入输出操作。
  • 在事件循环中,Node.js 会执行代码的其他部分,这些部分不包含阻塞操作。
  • 现在,一个回调函数会将文件数据记录到控制台。