When is the thread pool used?

So I have an understanding of how Node.js works: it has a single listener thread that receives an event and then delegates it to a worker pool. The worker thread notifies the listener once it completes the work, and the listener then returns the response to the caller.

My question is this: if I stand up an HTTP server in Node.js and call sleep on one of my routed path events (such as "/test/sleep"), the whole system comes to a halt. Even the single listener thread. But my understanding was that this code is happening on the worker pool.

Now, by contrast, when I use Mongoose to talk to MongoDB, DB reads are an expensive I/O operation. Node seems to be able to delegate the work to a thread and receive the callback when it completes; the time taken to load from the DB does not seem to block the system.

How does Node.js decide to use a thread pool thread vs the listener thread? Why can't I write event code that sleeps and only blocks a thread pool thread?

53070 次浏览

因此,我对 Node.js 的工作原理有一个了解: 它有一个接收事件并将其委托给工作者池的侦听器线程。工作线程在完成工作后通知侦听器,然后侦听器将响应返回给调用者。

这不太准确。Js 只有一个执行 javascript 的“ worker”线程。节点内部有处理 IO 处理的线程,但是将它们视为“工作者”是一种误解。实际上只有 IO 处理和节点内部实现的一些其他细节,但是作为程序员,除了一些混合参数(如 MAX _ LISTENERS)之外,您不能影响它们的行为。

我的问题是: 如果我在 Node.js 中启动一个 HTTP 服务器,并在路由路径事件(例如“/test/sleep”)上调用 sleep,那么整个系统将停止运行。即使是单个侦听器线程。但我的理解是,这些代码发生在工人池上。

JavaScript 中没有睡眠机制。我们可以更具体地讨论这个问题,如果你发布一个代码片段,你认为“睡眠”意味着什么。例如,在 python 中没有这样的函数来调用来模拟类似 time.sleep(30)的东西。有 setTimeout,但这基本上不是睡眠。setTimeoutsetInterval显式地 释放,而不是阻塞事件循环,这样其他位的代码可以在主执行线程上执行。您唯一能做的就是使用内存中的计算繁忙地循环 CPU,这确实会使主执行线程处于饥饿状态,并使您的程序无法响应。

Js 如何决定使用线程池线程而不是侦听器线程?为什么我不能编写睡眠且只阻塞线程池线程的事件代码?

网络 IO 总是异步的。就这样。磁盘 IO 同时具有同步和异步 API,因此没有“ decision”。Js 将根据调用 sync 与普通异步的 API 核心函数进行操作。例如: fs.readFilefs.readFileSync。对于子进程,还有单独的 child_process.execchild_process.execSync API。

经验法则总是使用异步 API。使用同步 API 的正当理由是在网络服务侦听连接之前对初始化代码进行初始化,或者使用不接受构建工具的网络请求的简单脚本进行初始化。

Your understanding of how node works isn't correct... but it's a common misconception, because the reality of the situation is actually fairly complex, and typically boiled down to pithy little phrases like "node is single threaded" that over-simplify things.

现在,我们将忽略通过 集群Webworker-线程的显式多处理/多线程,只讨论典型的非线程节点。

节点在单个事件循环中运行。它是单线程的,你只能得到一个线程。您编写的所有 javascript 都在这个循环中执行,如果在这个代码中发生了阻塞操作,那么它将阻塞整个循环,除非它结束,否则不会发生任何其他事情。这是您经常听到的节点的典型的单线程特性。但这不是全部。

某些通常用 C/C + + 编写的函数和模块支持异步 I/O。当您调用这些函数和方法时,它们在内部管理将调用传递给工作线程。例如,当您使用 fs模块请求一个文件时,fs模块将该调用传递给一个工作线程,该工作线程等待它的响应,然后它将响应返回到同时没有响应的事件循环。所有这些都是从节点开发人员抽象出来的,其中一些是通过使用 利布夫从模块开发人员抽象出来的。

正如 Denis Dollfus 在评论中指出的(从 这个答案到类似的问题) ,libuv 用来实现异步 I/O 的策略并不总是一个线程池,特别是在 http模块的情况下,此时似乎使用了不同的策略。为了我们的目的,注意异步上下文是如何实现的(通过使用 libuv) ,以及 libuv 维护的线程池是该库为实现异步提供的多种策略之一,这一点非常重要。


在一个主要相关的切线上,有一个更深入的分析如何实现节点的异步性,以及一些相关的潜在问题和如何处理它们,在这篇优秀的文章中。大部分内容扩展了我上面写的内容,但是它还指出:

  • 您在项目中包含的任何使用本机 C + + 和 libuv 的外部模块都可能使用线程池(请考虑: 数据库访问)
  • Libuv 的默认线程池大小为4,并使用一个队列来管理对线程池的访问——结果是,如果有5个长时间运行的 DB 查询同时运行,其中一个(以及任何其他依赖于线程池的异步操作)将等待这些查询在启动之前完成
  • 你可以通过增加线程池的大小来减轻这种环境变量,只要你在需要和创建线程池之前这样做:

如果你想在节点中实现传统的多处理或多线程,你可以通过内置的 cluster模块或者其他各种模块,比如上面提到的 webworker-threads,或者你可以通过实现某种方式将你的工作分块,然后手动使用 setTimeoutsetImmediateprocess.nextTick来暂停你的工作,并在以后的循环中继续工作,让其他进程完成(但是不推荐这样做)。

请注意,如果您使用 javascript 编写长时间运行/阻塞代码,那么您可能犯了一个错误。其他语言的执行效率要高得多。

这种误解仅仅是先发制人多任务和合作多任务之间的区别..。

睡眠会让整个嘉年华停止,因为所有的游乐设施都只有一条线,而你关上了大门。把它想象成“一个 JS 解释器和其他一些东西”,忽略这些线程... 对于您来说,只有一个线程,..。

...so don't block it.

线程池何时和谁使用:

首先,当我们在计算机上使用/install Node 时,它会在其他进程中启动一个进程,这个进程在计算机中被称为节点进程,它会一直运行,直到您终止它。这个运行过程就是我们所谓的单线程。

enter image description here

因此,单线程的机制使得阻塞节点应用程序变得容易,但这是 Node.js 带给表的独特特性之一。因此,如果您运行您的节点应用程序,它将只在单个线程中运行。无论同时有1个或100万个用户访问您的应用程序。

So let's understand exactly what happens in the single thread of nodejs when you start your node application. At first the program is initialized, then all the top-level code is executed, which means all the codes that are not inside any callback function (请记住,所有回调函数中的所有代码都将在事件循环中执行).

然后,所有执行的模块代码注册所有回调,最后为应用程序启动事件循环。

enter image description here

因此,正如我们在前面讨论的那样,这些函数中的所有回调函数和代码都将在事件循环中执行。在事件循环中,负载分布在不同的阶段。无论如何,我不打算在这里讨论事件循环。

为了更好地理解线程池,我要求你们想象一下,在事件循环中,一个回调函数中的代码在完成另一个回调函数中的代码执行之后执行,现在如果有一些任务实际上太重了。然后他们会阻止我们的 nodejs 单线程。这就是线程池的作用所在,就像事件循环一样,由 libuv 库提供给 Node.js。

所以线程池不是 nodejs 本身的一部分,它是由 libuv 提供的,用于将繁重的任务卸载给 libuv,libuv 将在自己的线程中执行这些代码,执行之后,libuv 将把结果返回给事件循环中的事件。

enter image description here

线程池为我们提供了四个额外的线程,它们与主线程完全分离。我们实际上可以配置多达128个线程。

所有这些线程组成了一个线程池。然后,事件循环可以自动将繁重的任务卸载到线程池。

有趣的是这一切都是在幕后自动发生的。决定什么进入线程池,什么不进入线程池的不是我们开发人员。

有许多任务流向线程池,例如

-> All operations dealing with files
->Everyting is related to cryptography, like caching passwords.
->All compression stuff
->DNS lookups