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



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

然后是工作线程,告诉我节点现在可以生成名为"的线程;工作线程";以通过CPU密集型任务,从而不会阻塞JavaScript堆栈。如果有了工作线程的力量,JavaScript实际上可以是多线程的,为什么人们把它称为单线程的呢?或者JavaScript确实是永久性的单线程,但有了工作线程的力量,一个进程就可以拥有多个JavaScript线程,而这些线程仍然是单线程的?

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

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

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

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

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

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

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

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

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

或者JavaScript确实是永久的单线程,但有了工作线程的力量,一个进程可以拥有多个JavaScript线程,这些线程仍然表现为单线程?

首先,JavaScript语言规范中没有任何固有的要求单线程的内容。单线程编程模型是JavaScript语言如何在流行的编程环境(如Node.js和浏览器)中实现的产物。因此,当谈到单线程性时,应该谈论编程环境(如Node.js),而不是语言本身。

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

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

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

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

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

HodeJs是单线程的,但为了防止该线程阻塞,NodeJ打开新线程以执行耗时的/CPU密集型工作。通过这样做,只要存在一种需求,当需求得到满足时销毁

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

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

最新更新