繁体   English   中英

Node.js 是否被视为带有工作线程的多线程?

[英]Is Node.js considered multithreading with worker threads?

我一生都认为 Node.js 和 JavaScript 是单线程语言。 Node.js 不适用于 CPU 密集型任务,但由于其单线程特性,它是轻量级的。 多线程对于 CPU 密集型任务很有用,因为您可以将任务委托给不同的线程,但它会为可能变得复杂的竞争条件创造机会。

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

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

此外,我阅读的这篇文章说的是上述声明。 这听起来像是 JavaScript 实际上一直在使用多个不同的线程。 为什么人们叫 JavaScript 单线程?

这听起来像是 JavaScript 实际上一直在使用多个不同的线程。 为什么人们叫 JavaScript 单线程?

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

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

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

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

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

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

或者 JavaScript 确实是永久单线程的,但是借助工作线程的力量,一个进程能够拥有 JavaScript 的多个线程,仍然表现单线程?

首先,JavaScript 语言规范中没有任何固有的需要单线程的东西。 单线程编程model是JavaScript语言在Node.js等流行编程环境和浏览器中实现的产物。 因此,在谈论单线程时,您应该谈论编程环境(例如 Node.js),而不是语言本身。

在 Node.js 中,一个进程现在可以拥有 JavaScript 的多个线程(使用 WorkerThreads)。 它们独立运行,因此您可以获得在多个线程中同时运行 JavaScript 的真正并行化。 为了避免线程同步的许多陷阱,WorkerThreads 在单独的 VM 中运行,并且不共享对其他 WorkerThreads 或主线程的变量的访问,除非非常小心地分配和控制 SharedMemory 缓冲区。 WorkerThreads 通常会使用通过事件循环运行的消息传递与主线程进行通信(因此,所有 JavaScript 线程都会强制执行一定程度的同步)。 消息不会以先发制人的方式在线程之间传递 - 这些通信消息流经事件循环,并且必须等待轮到它们处理,就像 Node.js 中的任何其他异步操作一样。

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

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

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

之所以称为单线程,是因为默认情况下,只有一个 JS 线程在 CPU 上运行。 就并发而言这听起来很奇怪,但它很好,因为使用了最少数量的资源。 NodeJs 旨在执行非阻塞操作,这意味着没有耗时或 CPU 密集型作业可以阻塞/挂起主应用程序。 因此,当调用 DB、写入文件、从另一台服务器获取数据等耗时操作时,NodeJs 会为该特定任务打开一个新线程。 通过这样做,主线程仍可用于侦听新事件,而 CPU 密集型和耗时的任务则在后台执行。 当该任务完成时,该线程被销毁。 由此,我可以推断

HodeJs 是单线程的,但为了防止该线程被阻塞,NodeJs 会打开新线程来执行耗时/CPU 密集型作业。 通过这样做,新线程在需要时打开,在需要时销毁

这整个过程优化了 CPU 资源管理。

请注意,NodeJs 不被认为是构建 CPU 密集型应用程序的理想选择。 我认为其原因是它可以打开很多新线程并且 CPU 可能会用完新线程。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM