因此,我了解Node.js的工作原理:它有一个侦听器线程,它接收一个事件,然后将其委托给一个工作池.工作线程在完成工作后通知侦听器,然后侦听器将响应返回给调用者.
我的问题是:如果我在Node.js中站起来一个HTTP服务器并在我的一个路由路径事件(例如"/ test/sleep")上调用sleep,整个系统就会停止运行.甚至是单个监听器线程.但我的理解是这个代码发生在工作池上.
现在,相比之下,当我使用Mongoose与MongoDB通信时,DB读取是一种昂贵的I/O操作.Node似乎能够将工作委托给一个线程并在完成时接收回调; 从DB加载所花费的时间似乎并没有阻止系统.
Node.js如何决定使用线程池线程与侦听器线程?为什么我不能编写睡眠的事件代码并且只阻塞线程池线程?
Jas*_*son 227
你对节点如何工作的理解是不正确的...但这是一种常见的误解,因为这种情况的实际情况实际上相当复杂,并且通常归结为像"节点是单线程"那样过分简化事物的简洁小词. .
目前,我们将忽略通过集群和webworker线程的显式多处理/多线程,并且只讨论典型的非线程节点.
节点在单个事件循环中运行.它是单线程的,你只能获得那个线程.您编写的所有javascript都在此循环中执行,如果在该代码中发生阻塞操作,那么它将阻止整个循环,并且在完成之前不会发生任何其他操作.这是您经常听到的节点的典型单线程特性.但是,这不是全局.
某些通常用C/C++编写的函数和模块支持异步I/O. 当您调用这些函数和方法时,它们会在内部管理将调用传递给工作线程.例如,当您使用fs模块请求文件时,fs模块会将该调用传递给工作线程,并且该工作程序等待其响应,然后它将返回到已经搅拌的事件循环,而不是在与此同时.所有这些都是从节点开发人员那里抽象出来的,其中一些是通过使用libuv从模块开发人员中抽象出来的.
正如Denis Dollfus在评论中指出的那样(从这个问题的答案),libuv用来实现异步I/O的策略并不总是一个线程池,特别是在http模块的情况下,一个不同的策略似乎是在这个时候使用.对于我们这里的目的,重要的是要注意异步上下文是如何实现的(通过使用libuv),并且libuv维护的线程池是该库提供的实现异步性的多种策略之一.
在这篇优秀的文章中,关于一个主要相关的切线,有一个更深入的分析节点如何实现异步性,以及一些相关的潜在问题以及如何处理它们.其中大部分都是基于我上面所写的内容,但它还指出:
UV_THREADPOOL_SIZE环境变量增加线程池的大小来缓解这种情况,只要您在需要和创建线程池之前执行此操作:process.env.UV_THREADPOOL_SIZE = 10;如果您想在节点中进行传统的多处理或多线程,您可以通过内置cluster模块或上述各种其他模块获取它webworker-threads,或者您可以通过实施某种方式来分块工作并手动使用setTimeout或setImmediate或者process.nextTick暂停你的工作并在以后的循环中继续它以让其他进程完成(但不建议这样做).
请注意,如果您在javascript中编写长时间运行/阻止代码,那么您可能犯了一个错误.其他语言的表现会更有效率.
Pet*_*ons 19
因此,我了解Node.js的工作原理:它有一个侦听器线程,它接收一个事件,然后将其委托给一个工作池.工作线程在完成工作后通知侦听器,然后侦听器将响应返回给调用者.
这不是很准确.Node.js只有一个执行javascript的"worker"线程.节点中有线程处理IO处理,但将它们视为"工作者"是一种误解.实际上只有IO处理和节点内部实现的一些其他细节,但作为程序员,除了一些misc参数(如MAX_LISTENERS)之外,您不能影响它们的行为.
我的问题是:如果我在Node.js中站起来一个HTTP服务器并在我的一个路由路径事件(例如"/ test/sleep")上调用sleep,整个系统就会停止运行.甚至是单个监听器线程.但我的理解是这个代码发生在工作池上.
JavaScript中没有睡眠机制.如果您发布了您认为"睡眠"的含义的代码片段,我们可以更具体地讨论这个问题.例如,没有这样的函数可以调用模拟time.sleep(30)python之类的东西.有setTimeout但是从根本睡不着.setTimeout并setInterval显式释放,而不是阻塞事件循环,以便其他代码位可以在主执行线程上执行.你唯一能做的就是忙着用内存计算来循环CPU,这确实会使主执行线程挨饿并使程序无响应.
Node.js如何决定使用线程池线程与侦听器线程?为什么我不能编写睡眠的事件代码并且只阻塞线程池线程?
网络IO始终是异步的.故事结局.磁盘IO具有同步和异步API,因此没有"决定".node.js将根据您调用sync和normal async的API核心函数来运行.例如:fs.readFilevs fs.readFileSync.对于子进程,还有单独的API child_process.exec和child_process.execSyncAPI.
经验法则总是使用异步API.使用同步API的正当理由是在网络服务中监听连接之前的初始化代码,或者是在不接受构建工具的网络请求的简单脚本中.
线程池如何使用以及何时使用:
首先,当我们在计算机上使用/安装 Node 时,它会在计算机中的其他进程中启动一个称为 node 进程的进程,并且它会一直运行直到您将其杀死。而这个运行过程就是我们所谓的单线程。
因此,单线程机制可以轻松阻止节点应用程序,但这是 Node.js 带来的独特功能之一。因此,如果您再次运行您的节点应用程序,它将仅在单个线程中运行。无论您有 1 或 100 万用户同时访问您的应用程序。
因此,让我们确切了解当您启动 node 应用程序时,nodejs 的单线程中发生了什么。首先初始化程序,然后执行所有顶层代码,这意味着所有不在任何回调函数内的代码(记住所有回调函数内的所有代码都会在事件循环下执行)。
之后,执行完所有模块代码,然后注册所有回调,最后,为您的应用程序启动事件循环。
因此,正如我们之前讨论的,所有回调函数和这些函数中的代码都将在事件循环下执行。在事件循环中,负载分布在不同的阶段。无论如何,我不打算在这里讨论事件循环。
好吧,为了更好地理解线程池,我要求您想象一下,在事件循环中,一个回调函数内部的代码在完成另一个回调函数内部代码的执行后执行,现在如果有一些任务实际上太重了。然后他们会阻塞我们的 nodejs 单线程。因此,这就是线程池的用武之地,就像事件循环一样,由 libuv 库提供给 Node.js。
所以线程池不是 nodejs 本身的一部分,它是由 libuv 提供的,用来将繁重的任务交给 libuv,libuv 会在自己的线程中执行这些代码,执行后 libuv 会将结果返回给事件循环中的事件。
线程池为我们提供了四个额外的线程,它们与主单线程完全分开。我们实际上最多可以将其配置为 128 个线程。
所以所有这些线程一起组成了一个线程池。然后事件循环可以自动将繁重的任务卸载到线程池。
有趣的是,这一切都是在幕后自动发生的。决定哪些进入线程池,哪些不进入的不是我们开发人员。
有很多任务去线程池,比如
-> All operations dealing with files
->Everyting is related to cryptography, like caching passwords.
->All compression stuff
->DNS lookups
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
39115 次 |
| 最近记录: |