Node.js中的asynchronous编程不应该导致StackOverflow?

我刚刚意识到(单线程)Node.js的一个问题:

  1. 服务器开始响应请求,并且请求运行直到它由于I / O而被阻塞。

  2. 当请求处理器阻塞时,服务器踢入并返回步骤#1,处理更多的请求。

  3. 每当请求处理器阻塞I / O时,服务器检查是否有任何请求完成。 它按照先进先出的顺序来处理客户端,然后像以前一样继续处理。

这并不意味着#2应该有一个堆栈溢出,如果太多的请求开始阻塞彼此,没有一个完成? 为什么/为什么不呢?

node.js可以防止你在任何地方使用asynchronous技术描述堆栈过度增长1

任何可能阻止使用callback进行进一步处理,而不是阻止呼叫。 这可以完全避免堆栈增长,并且很容易重新进入事件循环(即“驱动”底层的真实I / O并请求调度)。

考虑这个伪代码:

 fun() { string = net.read(); processing(string); } 

线程在读取时被阻塞,只有在读取完成后,堆栈才能被释放,并且processing完成。

现在,如果你所有的代码是这样的:

 fun() { net.read(onDone: processing(read_data)); } 

如果你实现这样的read

 net.read(callback) { iorequest = { read, callback }; io.push_back(iorequest); } 

只要read可以将读取的I / O与相关联的callback排队, fun就完成了。 fun的堆栈没有被阻塞 – 它立即返回到事件循环,没有任何线程堆栈剩余。

也就是说,您可以继续进行下一个callback(重新进入事件循环),而不在线程堆栈上保留任何每个请求的数据。

所以node.js通过在“用户”代码中阻塞调用的地方使用asynchronouscallback来避免栈过度增长。

要了解更多信息,请查看node.js '页面,并链接到第一组幻灯片。

1 好吧,差不多我猜


您在评论中提到了QueueUserAPC 。 通过这种types的处理,排队的APC被允许阻塞,并且队列中的下一个APC在线程堆栈上得到处理,从而成为“recursion”调度。

假设我们有三个APC待定( ABC )。 我们得到:

初始状态:

 Queue ABC Stack xxxxxxxx 

线程hibernate所以APC调度启动,进入A处理:

 Queue BC Stack AAAAxxxxxxxx 

A块,B被分派在同一个栈上

 Queue C Stack BBBBBBAAAAxxxxxxxx 

B块,C调度:

 Queue Stack CCCCCCCBBBBBBAAAAxxxxxxxx 

清楚地看到,如果足够的阻塞APC悬而未决,堆栈将最终炸毁。

使用node.js ,请求不被允许阻塞 。 相反,这里是一个模拟相同的三个请求会发生什么:

 Queue ABC Stack xxxxxxxx 

A开始处理:

 Queue BC Stack AAAAxxxxxxxx 

现在,A需要做一些阻止 – 在node.js ,它实际上不能 。 它做的是排队另一个请求( A' )(大概是一个上下文 – 简单地把所有的variables都用散列):

 I/O queue A' Queue BC Stack AAAAxxxxxxxx 

然后A 返回并回到:

 I/O queue A' Queue BC Stack xxxxxxxx 

注意:不再有堆栈。 I / O挂起队列实际上由OSpipe理(使用epollkqueue或其他)。 主线程检查事件循环中OS I / O就绪状态和未决(需要CPU)队列。

然后B得到一些CPU:

 I/O queue A' Queue C Stack BBBBBBBxxxxxxxx 

同样的事情,B想做I / O。 它排队一个新的callback并返回

 I/O queue A'B' Queue C Stack xxxxxxxx 

如果B的I / O请求同时完成,则下一个快照可能如下所示

 I/O queue A' Queue B' Stack CCCCCxxxxxxxx 

处理线程上没有多个callback堆栈帧。 阻止调用不是由API提供的,该堆栈不显示APC模式所执行的recursion增长的types。

node.js基于Google的V8 JavaScript引擎,它使用了Event Loop。

看到

Node.js中的事件循环与使用线程不同,有几个关键方面。

在Node.js中,运行时不会中断你的函数,开始执行另一个函数。 相反,您必须在Node.js并发性启动之前从当前函数返回

 function readAndWriteItem(id) { var callback = function(item) { item.title = item.title + " " + item.title; writeItem(item); }; readItem(id, callback); }; 

在这个例子中,创build一个callback闭包,并readItem 。 据推测,当查询结果准备就绪时, readItem会排队查询的发出,并设置自己的内部callback来执行。 所以这个示例函数readAndWriteItem只是排队一条消息,通过线路发送,设置更多的callback, 并立即返回 。 一旦这个函数返回,Node.js可以处理它的事件循环魔术。

由于该函数已经返回 ,并且由于在使用Node.js时全局都是这种情况,所以没有堆栈溢出。