Node.js 是否完全基于单线程
Node.js是一个开源的、跨平台的运行时环境,用于在浏览器之外执行JavaScript代码。在这篇文章中,我们将深入探讨一下,了解一下Node.js是否完全基于单线程。但在开始之前,让我们先澄清一些基础知识:
- 进程: 当一个程序开始运行时,它创建了一个进程,这是一个正在执行的程序。一个进程中可以包含多个线程。
- 线程: 线程是一组可以由调度程序(通常是操作系统的一个组件)独立控制的编程指令,最小的单位。主要的区别是,进程运行在不同的内存区域,而同一进程中的线程使用共享的内存空间。
- 多处理: 在单个计算机系统中使用两个或更多个CPU(处理器)的情况称为多处理。由于现在有许多处理器可供使用,因此可以同时运行多个进程。
- 多线程: 多线程是一种执行体系结构,允许许多代码块(线程)在单个进程的“上下文”中同时运行。
- 线程池: 线程池是一组先前创建但尚未被使用的线程集合,它们正在等待执行任务。通过保持线程池,可以减少频繁生成和销毁线程对于快速活动而导致的执行延迟。
所谓基于单线程的框架是什么意思?
当我们说一个框架是单线程的时候,意思是只有一个语句可以在一个时间执行。调用栈顶部的内容以非阻塞的方式被调用。
以下是Node.js服务器架构示意图:
Node.js使用 单线程事件循环 设计来管理众多并发客户端。JavaScript基于事件的架构和JavaScript回调机制作为Node.js处理模型的基础。一个单线程用于执行Node.js应用程序,同时也包含了事件循环。事件循环是Node.js处理模型的核心。让我们试着理解事件循环的工作原理。
事件循环: 事件循环一次只能执行一个进程。这意味着它一次只能执行一个函数,由于函数可以包含多个指令,事件循环一次只会执行一个指令。事件循环的美妙之处在于它有能力“搁置”耗时的I/O活动,以便其他指令可以继续执行而不受中断。这与在单线程上运行所有内容是不同的。这就是为什么尽管可能有许多用户同时向Node.js API发送查询,我们能够快速得到结果。因此,我们可以说Node.js是单线程的。
Node.js如何作为异步工作?
然而,通过使用工作线程,我们可以将指令作为异步模型执行。在内部,Node.js利用了 libuv 库,这是一个管理与操作系统相关任务的C++库,如并发、网络和基于异步I/O的操作系统。
该库创建了一个包含四个线程的线程池,用于执行与操作系统相关的任务,同时利用所有CPU核心的处理能力。它还有助于管理所有其他线程。因此,在某些情况下,libuv为Node.js提供了多线程的能力。
为了证明这一点,我们来看一个示例:
示例1: 在这个示例中,我使用了加密库中的pbkdf2函数。我记录了从程序开始到运行特定函数调用所需的时间。
import crypto from 'crypto';
const startTime = Date.now();
function logTime() {
crypto.pbkdf2("a", "b", 100000, 512, "sha512", () => {
console.log("Hash: ", Date.now() - startTime);
});
}
logTime();
logTime();
logTime();
logTime();
输出:
解释: 如您所见,四个函数调用几乎同时完成。这在单线程系统中是不可能的。因此,在上述情况中使用了多线程。
示例2: 在这个示例中,我们将调用函数logTime五次:
import crypto from 'crypto';
const startTime = Date.now();
function logTime() {
crypto.pbkdf2("a", "b", 100000, 512, "sha512", () => {
console.log("Hash: ", Date.now() - startTime);
});
}
logTime();
logTime();
logTime();
logTime();
logTime();
输出:
解释: 如你所见,前四个函数调用几乎同时执行,而 最后一个函数调用需要很长时间。 由于计算机有四个核心,前四个函数调用同时执行,每个核心执行一个函数调用,而最后一个函数调用在任何一个核心空闲时立即运行。