Node.js 是否考虑使用工作线程进行多线程?

Jpa*_*061 8 javascript multithreading node.js

在我的一生中,我认为 Node.js 和 JavaScript 是一种单线程语言。Node.js 不适合 CPU 密集型任务,但由于其单线程性质,它是轻量级的。多线程适用于 CPU 密集型任务,因为您可以将任务委托给不同的线程,但它为竞争条件创造了可能变得复杂的开放。

然后是工作线程,告诉我节点现在可以产生名为“工作线程”的线程来传递 CPU 密集型任务,因此它不会阻塞 JavaScript 堆栈。为什么人们把 JavaScript 称为单线程,就像一个永久的定义,如果有了工作线程的力量,它实际上可以是多线程的?或者 JavaScript 确实是永久单线程的,但是借助工作线程的强大功能,一个进程可以拥有多个 JavaScript 线程,而这些线程仍然是单线程的?

Node.js 使用两种线程:一个由事件循环处理的主线程和工作池中的几个辅助线程。

另外,我读过的这篇文章说了上面的说法。这听起来就像 JavaScript 一直在使用多个不同的线程。为什么人们将 JavaScript 称为单线程?

jfr*_*d00 17

这听起来就像 JavaScript 一直在使用多个不同的线程。为什么人们将 JavaScript 称为单线程?

Node.js 中的编程模型是一个单线程事件循环,可以访问异步操作,这些操作使用本机代码为某些操作(磁盘 I/O、网络、计时器、某些加密操作等)实现异步行为。

另外,请记住,这种编程模型不是 JavaScript 语言本身的产物。它是 JavaScript 在流行环境(如 Node.js 和浏览器)中作为事件驱动实现部署的产物。

内部有一个本地代码线程池用于实现一些异步操作,例如文件 I/O 或一些加密操作,这一事实并没有改变编程模型是单线程事件循环的事实。线程池就是如何通过 JavaScript 使耗时任务的实现具有异步接口。这是一个实现细节,不会从单线程、事件循环模型改变 JavaScript 编程模型。

类似地,您现在可以实际创建 WorkerThread 的事实并没有真正改变主要的编程模型,因为 WorkerThread 在具有单独事件循环的单独 JavaScript VM 中运行并且不共享常规变量。因此,无论您是否使用 WorkerThreads,您仍然可以为事件驱动的非阻塞系统设计代码。

WorkerThreads 确实允许你卸载一些耗时的任务,让它们脱离主事件循环,以保持主事件循环更具响应性,在某些情况下这是一个非常好的和有用的选择。但是,整体模型不会改变。例如,所有网络仍然是事件驱动的、非阻塞的、异步的。因此,仅仅因为我们有 WorkerThreads,这并不意味着您现在可以像有时在 Java 中那样用 JavaScript 编程网络,为每个新传入请求使用单独的线程。JavaScript 模型的那部分根本没有改变。如果您在 Node.js 中有一个 HTTP 服务器,它仍然一次接收一个传入请求,并且不会开始处理下一个传入请求,直到前一个传入请求将控制权返回给事件循环。

此外,您应该意识到 Node.js 中 WorkerThreads 的当前实现是相当重量级的。WorkerThread 的创建会启动一个新的 JavaScript 虚拟机,初始化一个新的全局上下文,设置一个新的堆,启动一个新的垃圾收集器,分配一些内存等......虽然在某些情况下很有用,但这些 WorkerThreads 很多,比操作系统级线程更重量级。我认为它们就像迷你子进程一样,但它们的优势是它们可以在 WorkerThreads 之间或主线程和 WorkerThreads 之间使用 SharedMemory,而这在实际的子进程中是无法做到的。

或者 JavaScript 确实是永久单线程的,但是借助工作线程的强大功能,一个进程可以拥有多个 JavaScript 线程,而这些线程仍然是单线程的?

首先,JavaScript 语言规范中没有任何内容需要单线程。单线程编程模型是 JavaScript 语言在 Node.js 和浏览器等流行编程环境中实现方式的产物。所以,当谈到单线程时,你应该说的是编程环境(比如 Node.js),而不是语言本身。

在 Node.js 中,一个进程现在可以拥有多个 JavaScript 线程(使用 WorkerThreads)。它们独立运行,因此您可以获得在多个线程中同时运行 JavaScript 的真正并行化。为了避免线程同步的许多陷阱,WorkerThreads 在单独的 VM 中运行,并且不共享对其他 WorkerThreads 或主线程的变量的访问,除非非常小心地分配和控制 SharedMemory 缓冲区。WorkerThreads 通常会使用通过事件循环运行的消息传递与主线程进行通信(因此,所有 JavaScript 线程都会以这种方式强制进行一定程度的同步)。

这是使用 WorkerThreads 的示例实现。我正在编写一个测试程序,它的工作是对一项活动进行数十亿次模拟,并记录所有结果的统计数据,以查看结果的随机性。模拟的某些部分涉及一些在 CPU 上非常耗时的加密操作。在我的第一代代码中,我运行了较少数量的迭代进行测试,但很明显,所需的数十亿次迭代需要花费数小时才能运行。

通过测试和测量,我能够找出代码的哪些部分使用了最多的 CPU,然后我创建了一个 WorkerThread 池(8 个工作线程),我可以将更耗时的作业传递给它,它们可以在平行线。这将运行模拟的总时间减少了 7 倍。

现在,我也可以为此使用子进程,但它们的效率会降低,因为我需要在主线程和 workerThread 之间传递大量数据缓冲区(workerThread 正在处理该缓冲区中的数据),而且数据量很大使用 SharedArrayBuffer 比在父进程和子进程之间传递数据更有效(这将涉及复制数据而不是共享数据)。