何时使用线程池?

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

我的问题是:如果我在Node.js中站起来一个HTTP服务器,并且在我的一个路由事件(比如“/ test / sleep”)上调用sleep,整个系统就会停止。 即使是单一的监听器线程。 但我的理解是,这个代码正在工作池上发生。

现在,相比之下,当我使用Mongoose与MongoDB交谈时,数据库读取是一个昂贵的I / O操作。 节点似乎能够将工作委托给线程,并在完成时接收callback; 从数据库加载所花的时间似乎不会阻塞系统。

Node.js如何决定使用线程池线程与侦听器线程? 为什么我不能编写睡眠事件代码,只能阻塞线程池线程?

你对节点是如何工作的理解是不正确的,但是这是一个常见的误解,因为情况的真实性实际上相当复杂,通常归结为像“节点是单线程”这样的简短短语,过度简化了事物。

目前,我们将忽略通过集群和webworker-threads的显式多处理/multithreading ,而只谈论典型的非线程节点。

节点在单个事件循环中运行。 它是单线程的,你只能得到一个线程。 所有你写的JavaScript都是在这个循环中执行的,如果在这个代码中发生阻塞操作,那么它将阻塞整个循环,直到它完成时才会发生。 这是您所听到的节点的典型单线程特性。 但是,这不是全貌。

通常用C / C ++编写的某些函数和模块支持asynchronousI / O。 当你调用这些函数和方法时,它们在内部pipe理将调用传递给工作线程。 例如,当你使用fs模块来请求一个文件时, fs模块将这个调用传递给一个工作者线程,并且这个工作者等待它的响应,然后这个响应呈现回事件循环,而没有它同时。 所有这些都是从你这个节点开发者那里抽象出来的,有些是通过使用libuv从模块开发者中抽象出来的。

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


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

  • 您在您的项目中使用本地C ++和libuv的任何外部模块都可能使用线程池(想想:数据库访问)
  • libuv的默认线程池大小为4,并使用队列来pipe理对线程池的访问 – 结果是,如果有5个长时间运行的数据库查询全部同时进行,则其中一个(以及任何其他asynchronous依赖于线程池的操作)将等待这些查询在他们开始之前完成
  • 您可以通过增加线程池的大小,通过UV_THREADPOOL_SIZE环境variables来缓解这个问题,只要在线程池需要和创build之前完成: process.env.UV_THREADPOOL_SIZE = 10;

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

请注意,如果您在JavaScript中编写长时间运行/阻止代码,则可能是错误的。 其他语言将更高效地执行。

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

这并不准确。 Node.js只有一个“worker”线程来执行javascript。 节点中有线程处理IO处理,但将其视为“工人”是一种误解。 实际上只有IO处理和节点内部实现的一些其他细节,但作为程序员,除了MAX_LISTENERS之类的一些misc参数之外,您不能影响其行为。

我的问题是:如果我在Node.js中站起来一个HTTP服务器,并且在我的一个路由事件(比如“/ test / sleep”)上调用sleep,整个系统就会停止。 即使是单一的监听器线程。 但我的理解是,这个代码正在工作池上发生。

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

Node.js如何决定使用线程池线程与侦听器线程? 为什么我不能编写睡眠事件代码,只能阻塞线程池线程?

networkingIO总是asynchronous的。 故事结局。 磁盘IO具有同步和asynchronousAPI,因此不存在“决定”。 node.js将根据您称之为同步与正常asynchronous的API核心函数行为。 例如: fs.readFile vs fs.readFileSync 。 对于subprocess,还有独立的child_process.execchild_process.execSync API。

经验法则总是使用asynchronousAPI。 使用同步API的有效原因是在networking服务中的初始化代码在监听连接之前,或者在不接受构build工具和networking请求的简单脚本中。

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

睡觉关掉了整个狂欢节,因为所有的游乐设施都有一条线路,你关上了大门。 把它看作“一个JS解释器和其他一些东西”,忽略线程…对于你来说,只有一个线程,…

…所以不要阻止它。