Nodejs的内部线程池是如何工作的?

15

我已经阅读了很多关于NodeJs运行方式的文章,但我仍然无法确定Nodejs内部线程如何处理IO操作。

在这个回答中https://dev59.com/_HnZa4cB1Zd3GeqPus_H#20346545 ,他说NodeJs的线程池中有4个内部线程用于处理I/O操作。那么如果有1000个请求同时到来,每个请求都想要执行从数据库检索大量数据的I/O操作。NodeJs将把这些请求分别交给这4个工作线程,而不会阻塞主线程。因此,NodeJs能够同时处理的最大I/O操作数量为4个操作。我理解得对吗?

如果我是正确的,那么其余的请求将在哪里处理呢?主单线程是非阻塞的,并继续将请求驱动到相应的操作者,那么当所有工作线程都满了时,这些请求会去哪里呢?它们是否拥有一个内部任务队列来存储这些请求?

在下面的图片中,所有的内部工作线程都忙于处理任务,假设它们都需要从数据库中检索大量数据,而主单线程则继续将新请求驱动到这些工作线程,那么这些请求将去哪里呢?它们是否有一个内部任务队列来存储这些请求?

enter image description here


你对C或C++有多熟悉? - slebetman
1
这1000个请求通过单个以太网/ WiFi卡传输。硬件本身只能处理字节流。除非您在计算机上连接了多个以太网卡并将其汇聚到多个调制解调器(通常意味着多个互联网帐户),否则不存在1000个并行网络请求。实际存在于现实世界中的是1000个网络请求轮流通过单个连接发送比特 - 快速执行此操作,您就会产生1000个并行请求的错觉。 - slebetman
1
由于在现实世界中通常不存在并行请求,因此Node.js避免了多线程的内存和上下文切换开销,而大多数人使用多线程(并非所有人都是如此 - 其他语言也开始变得聪明,例如Java的Netty将所有网络处理都放在单个线程上)。Node.js以并行方式等待而不是尝试并行执行代码。等待需要完全0%的CPU时间。 - slebetman
3个回答

13

libuv提供的单个进程线程池默认创建4个线程。可以使用UV_THREADPOOL_SIZE环境变量来改变在node进程启动时创建的线程数,最大值为1024(从libuv版本1.30.0开始)。

当所有线程都被阻塞时,进一步请求将排队等待。用于请求线程的API方法称为uv_queue_work

这个线程池用于任何会导致阻塞IO的系统调用,包括本地文件系统操作。它也可以用于减少CPU密集型操作的影响,就像@Andrey提到的那样。

大多数网络操作支持的非阻塞IO不需要使用线程池。

如果您正在使用的数据库驱动程序的源代码可用,并且能够找到对uv_queue_work的引用,则可能正在使用线程池。

如果需要,libuv线程池文档提供更多技术细节。


3
在下面的图片中,所有内部工作线程都充满了任务,假设它们都需要从数据库检索大量数据,并且主要单个线程不断向这些工作线程发送新请求。
这不是node.js如何使用这些线程的方式。
根据Node.js文档,线程的使用方式如下:

enter image description here

所有请求和响应都在主线程中“处理”。您的回调函数(以及await后面的代码)只是轮流执行。javascript解释器和“事件循环”之间的“循环”通常只是一个while循环。
除了自己启动的worker_threads外,node.js仅使用4个线程:等待DNS响应、磁盘I/O、内置加密库和内置zip库。Worker_threads是node.js在主线程之外执行javascript的唯一地方。所有其他线程的使用执行C/C++代码。
如果您想了解更多信息,则我已经撰写了几篇相关问题的答案: Node js架构和性能

Node.js服务器比基于线程的服务器更好的原因

当回调函数执行时,传入事件会发生什么?Node.js解释

JavaScript是否使用弹性跑道算法进行处理?

有没有其他方法可以实现“监听”功能而不需要无限循环?


你尝试过在分叉的集群实例下使用多个worker_thread吗?如果加密使用更多的worker线程,那么我想它应该可以正常工作吧? - user2734550

1
没有,线程池的主要用途是卸载CPU密集型操作。IO在一个线程中执行 - 如果您正在并行等待外部数据,则不需要多个线程,并且事件循环正是一种组织执行流程的技术,以便您尽可能并行等待。
例子: 您需要发送100封电子邮件询问问题(y / n)和另一封回答“y”的数量。编写电子邮件大约需要30秒,平均需要两个小时才能回复+ 10秒钟来阅读响应。您首先编写所有100封电子邮件(需要50分钟时间),然后等待警报声,每次收到答案时都会提醒您,并且随着您收到答案,您会增加“y”的数量。在大约2小时50分钟内,您就完成了。这是异步IO和事件循环的示例(无需线程池)。
阻塞示例:发送电子邮件,等待答案,重复。需要4天(如果您可以克隆另一个您,则为两天)。
异步线程池示例:每个响应都是您不知道的语言。您有4个翻译朋友。您将文本通过电子邮件发送给他们,他们将翻译后的文本通过电子邮件发送回给您(或更准确地说:您打印文本并将其放入“需要翻译”文件夹中。每当有可用的翻译器时,从文件夹中提取文本)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接