我不是Node程序员,但是我对单线程无阻塞IO模型的工作方式很感兴趣。
在我阅读了理解“ node-js-event-loop”的文章后,我真的很困惑关于它。
给出了该模型的示例:

c.query(
   'SELECT SLEEP(20);',
   function (err, results, fields) {
     if (err) {
       throw err;
     }
     res.writeHead(200, {'Content-Type': 'text/html'});
     res.end('<html><head><title>Hello</title></head><body><h1>Return from async DB query</h1></body></html>');
     c.end();
    }
);


队列:当有两个请求A(首先出现)和B时,因为只有一个服务器端程序将在一个线程中处理请求A首先:执行SQL查询是代表I / O等待的sleeping语句。并且该程序被卡在I/O等待中,无法执行将网页渲染到后面的代码。在等待期间程序会切换到请求B吗?我认为,由于是单线程模型,因此无法将一个请求与另一个请求进行切换。但是示例代码的标题表明,除了您的代码之外,所有其他内容都可以并行运行。


(PS我不确定我是否误解了代码,因为我从未使用过Node。)在等待期间,Node如何将A切换到B?可以
用一种简单的方式解释Node的单线程无阻塞IO模型吗?如果您能帮助我,我将不胜感激。 :)


#1 楼

Node.js基于libuv构建,libuv是一个跨平台的库,它为支持的操作系统(至少为Unix,OS X和Windows)提供的异步(非阻塞)输入/输出抽象api / syscall。

异步IO

在此编程模型中,对由文件系统管理的设备和资源(套接字,文件系统等)的打开/读取/写入操作不会阻塞调用线程(如(类似于典型的类似C的同步模型),并仅在新数据或事件可用时标记要通知的进程(在内核/ OS级数据结构中)。如果是类似Web服务器的应用程序,则该过程负责确定通知事件属于哪个请求/上下文,并从那里继续处理请求。请注意,这必然意味着您将与向OS发出请求的堆栈位于不同的堆栈框架上,因为OS必须屈服于进程的调度程序才能使单线程进程处理新事件。 />
我描述的模型的问题在于,它对程序员不熟悉并且很难推理,因为它本质上是非顺序的。 “您需要在函数A中进行请求,并在另一个函数中处理结果,而通常情况下,您无法使用A中的本地人。”

节点的模型(连续传递样式和事件循环)

Node通过诱使程序员采用某种编程风格,利用javascript的语言功能解决了该问题,使该模型看起来更具同步性。每个请求IO的功能都具有function (... parameters ..., callback)之类的签名,并且需要给其提供一个回调,该回调将在请求的操作完成时被调用(请注意,大部分时间都在等待OS发出完成信号,这是可以花在做其他工作上)。 Javascript对闭包的支持使您可以使用在回调主体内部的外部(调用)函数中定义的变量-这样可以保留节点运行时将独立调用的不同函数之间的状态。另请参见Continuation Passing Style。此外,在调用产生了IO操作的函数后,调用函数通常会return控制节点的事件循环。此循环将调用计划执行的下一个回调或函数(很可能是因为相应的事件已由OS通知)-这允许并发处理多个请求。

您可以想到节点的事件循环有点类似于内核的调度程序:内核将调度其阻塞的IO完成后执行阻塞的线程,而节点将调度发生相应事件时的回调。

高度并发,没有并行性

最后,短语“除了代码之外的所有内容并行运行”在捕捉节点允许您的代码通过多路复用和排序所有js并发处理单个线程同时处理来自成千上万个开放套接字的请求这一点上做得很不错单个执行流中的逻辑(即使说“一切并行运行”在这里可能不正确-请参阅并发与并行性-有什么区别?)。这对于webapp服务器非常有效,因为实际上大部分时间都花在等待网络或磁盘(数据库/套接字)上,并且逻辑实际上并不占用大量CPU,也就是说:这对于IO绑定的工作负载非常有效。 br />

评论


后续问题:I / O实际如何发生?节点正在向系统发出请求,并要求在完成时收到通知。那么系统是在运行正在执行I / O的线程,还是系统也在使用中断在硬件级别异步执行I / O?某处必须等待I / O完成,这将阻塞直到完成并消耗一些资源。

–菲利普
13年11月13日在19:24



刚刚注意到此后续评论由下面的@ user568109回答,我希望有一种合并这两个答案的方法。

– lfalin
2014年12月30日在1:04

希望您能写出两倍的回复,所以我会理解得更好。

–拉斐尔·埃因(Rafael Eyng)
2015年5月11日下午4:40

记录中很多地方都支持Node。当我为MIPS32路由器设计固件时,可以通过OpenWRT在其中运行Node.JS。

– Qix-蒙尼卡(MS)被盗
15年5月26日在1:56

@Philip有一种方法将永远不需要轮询。了解有关硬件中断的信息。诸如磁盘之类的设备(使用文件处理程序代表诸如以太网适配器之类的实际物理接口)可以通过硬件中断向操作系统发出信号,表明已准备好一些数据。 Wikipedia在en.wikipedia.org/wiki/Asynchronous_I/O上说:“直接内存访问(DMA)可以大大提高基于轮询的系统的效率,而硬件中断可以完全消除对轮询的需求。”

– zafar142003
17年3月9日在7:30

#2 楼

好吧,从某种角度来说,让我比较一下node.js和apache。
Apache是​​一个多线程HTTP服务器,对于服务器收到的每个请求,它都会创建一个单独的线程来处理该请求。另一方面,Node.js是事件驱动的,从单个线程异步处理所有请求。
当在apache上接收到A和B时,会创建两个线程来处理请求。每个都分别处理查询,每个都在为页面提供服务之前等待查询结果。该页面仅在查询完成之前提供。查询提取被阻塞,因为服务器直到接收到结果才执行线程的其余部分。
在节点中,c.query是异步处理的,这意味着,尽管c.query提取A的结果,但它会跳转以处理c。查询B,当结果到达A时,它将结果发送回回调,该回调发送响应。 Node.js知道在提取完成时执行回调。

我认为,因为它是单线程模型,所以无法
从一个请求切换到另一个请求。

实际上,节点服务器一直在为您做这些事情。为了进行切换,(异步行为)您将使用的大多数功能都将具有回调。
编辑
SQL查询来自mysql库。它实现了回调样式以及事件发射器来对SQL请求进行排队。它不会异步执行它们,这是由提供非阻塞I / O抽象的内部libuv线程完成的。进行查询的步骤如下:

打开与db的连接,连接本身可以异步进行。
一旦连接了db,查询就会传递到服务器。可以将查询排队。
主事件循环通过回调或事件获得完成通知。
主循环执行您的回调/事件处理程序。

以类似的方式处理对http服务器的传入请求。内部线程体系结构是这样的:

C ++线程是libuv线程,它们执行异步I / O(磁盘或网络)。在将请求分派到线程池之后,主事件循环继续执行。它可以等待或休眠,因此可以接受更多请求。 SQL查询/ HTTP请求/文件系统读取都是以这种方式发生的。

评论


图表非常有用。

– Anmol Saraf
2014年11月26日下午13:46

等等,所以在您的图表中,您具有“内部C ++线程池”,这意味着所有IO阻塞操作都将产生一个线程,对吗?因此,如果我的Node应用程序为每个请求执行一些IO,那么Node模型和Apache模型之间实际上没有区别吗?我没有让这部分对不起。

–gav.newalkar
15年1月27日在5:17

@ gav.newalkar他们不产生线程,请求被排队。线程池中的线程处理它们。线程不是动态的,每个请求都不像Apache中那样。它们通常是固定的,并且因系统而异。

–user568109
15年1月27日在6:58

@ user568109但是Apache也在使用线程池(httpd.apache.org/docs/2.4/mod/worker.html)。因此,最终,使用node.js进行设置之间的区别与仅在线程池所在的位置上使用Apache进行前面的安装有所不同,不是吗?

–克里斯
16年4月10日在15:59

该图应在官方文档的第一页上。

– Bouvierr
16年5月5日在15:42

#3 楼

Node.js在后台使用libuv。 libuv有一个线程池(默认情况下大小为4)。因此,Node.js确实使用线程来实现并发性。

但是,您的代码在单个线程上运行(即,Node.js函数的所有回调都将在同一线程上调用,因此称为循环线程或事件循环)。当人们说“ Node.js在单个线程上运行”时,他们实际上是在说“ Node.js的回调在单个线程上运行”。

评论


简短但明确的答案(y)

– Sudhanshu Gaur
17年7月24日在15:03

好的答案我要补充一点,I / O发生在此主事件循环,循环线程,请求线程之外

–伊诺特·波帕
18年1月2日在13:39

这就是我从2个小时开始搜索的答案,如何在单线程应用程序中管理并发性

–穆罕默德·拉姆赞(Muhammad Ramzan)
18年1月12日在15:27

是的,很难获得“下一级”的答案。这说明了IO实际完成的位置(在其他地方的线程池中)

–奥利弗·肖
18年2月4日在20:23

#4 楼

Node.js基于事件循环编程模型。事件循环在单线程中运行,并反复等待事件,然后运行订阅这些事件的所有事件处理程序。例如,事件可以是


定时器等待已完成
下一个数据块已准备好写入该文件
另外还有一个崭新的HTTP请求即将到来

所有这些都在单线程中运行,并且没有JavaScript代码可以并行执行。只要这些事件处理程序很小,并且等待更多事件本身,一切就可以顺利进行。这允许通过单个Node.js进程同时处理多个请求。

(事件发生的源头有点不可思议。其中一些涉及并行运行的低级工作线程。)

在这种SQL情况下,在进行数据库查询与在回调中获取其结果之间发生了很多事情(事件)。在这段时间内,事件循环不断为应用程序注入生命,并一次将一个小事件推进到其他请求中。因此,可以同时处理多个请求。




根据:“从10,000英尺开始的事件循环-Node.js背后的核心概念” 。

#5 楼

函数c.query()具有两个参数。在这种情况下,“获取数据”操作是一个DB-Query,现在可以由Node处理。产生工作线程并执行DB-Query任务来完成js。 (请记住,Node.js可以在内部创建线程)。这使函数能够立即返回而没有任何延迟

第二个参数“数据的后处理”是一个回调函数,节点框架注册该回调并由事件循环调用。 >
因此,语句c.query (paramenter1, parameter2)将立即返回,使节点能够满足另一个请求。

PS:我才刚刚开始理解节点,实际上我想将此内容写为@Philip但由于信誉度不足,因此将其写为答案。

#6 楼

如果您进一步阅读-“当然,在后端,有用于数据库访问和进程执行的线程和进程。但是,这些线程和进程没有显式地暴露给您的代码,因此您不必担心它们,除非知道从每个请求的角度来看,例如与数据库或与其他进程的I / O交互将是异步​​的,因为这些线程的结果是通过事件循环返回到您的代码的。“

关于- “除了代码之外,所有内容并行运行”-您的代码是同步执行的,只要您调用异步操作(如等待IO),事件循环就会处理所有内容并调用回调。

在您的示例中:有两个请求A(首先出现)和B。执行请求A,代码继续同步运行并执行请求B。事件循环处理请求A,完成后将调用请求A的回调并返回结果,请求B也是如此。

评论


“当然,在后端,有一些线程和进程用于数据库访问和进程执行。但是,这些线程和进程并未显式地暴露给您的代码。”-如果我从这句话中得出,那么我看不出哪个Node之间有任何区别或任何多线程框架-假设Java的Spring框架-都可以。有线程,但是您不能控制它们的创建。

–拉斐尔·埃因(Rafael Eyng)
2015年5月12日23:38

@RafaelEyng我认为要处理一系列的多个请求,node总是会有一个线程。我不确定是否除了数据库访问等其他进程之外,是否将每个回调都放在线程的新实例上,但是至少我们可以肯定地知道,节点每次接收请求时都不会实例化线程,该请求必须在处理之前排队(执行之前回调)。

–冷地狱犬
19年11月20日在7:10

#7 楼

好的,到目前为止,大多数事情应该都已经清楚了……最棘手的部分是SQL:如果实际上不是在另一个线程或整个进程中整体运行,则必须将SQL执行分解为各个步骤(通过SQL处理器用于异步执行!),其中执行非阻塞线程,而阻塞线程(例如睡眠)实际上可以传输到内核(作为警报中断/事件),并放在事件列表中。主循环。

这意味着,例如SQL等的解释是立即完成的,但是在等待期间(内核将其存储为将来在某个kqueue,epoll,...结构中存储的事件;以及其他IO操作)主循环可以做其他事情,并最终检查这些IO是否发生了事情并等待。

因此,再次重新说明一下:程序永远不会(允许阻塞),休眠调用也不会执行。它们的职责是由内核(写一些东西,等待一些东西通过网络,等待时间过去)或另一个线程或进程来完成的。 –在每个事件循环周期中,Node进程仅在对OS的唯一阻塞调用中检查内核是否至少完成了其中一项职责。完成所有非阻塞操作后,就达到了这一点。

清除吗? :-)

我不知道Node。但是c.query是从哪里来的?

评论


kqueue epoll用于Linux内核中的可伸缩异步I / O通知。 Node具有libuv。节点完全位于用户区。它不依赖于内核实现什么。

–user568109
13年11月16日在14:21

@ user568109,libuv是Node的中间人。任何异步框架都(直接或不直接)依赖于内核中的某些异步I / O支持。所以?

– Robert Siemer
13年11月17日在1:59

对困惑感到抱歉。套接字操作需要来自内核的非阻塞I / O。它负责异步处理。但是异步文件I / O由libuv本身处理。您的回答并没有说明。两者都一样,由内核处理。

–user568109
13年11月17日在8:44