Node.js中的异步编程不应该导致StackOverflow吗?

时间:2012-01-15 10:10:48

标签: javascript node.js

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

  1. 服务器开始响应请求,请求一直运行,直到它因I / O而阻塞。

  2. 当请求处理器阻止时,服务器启动并返回步骤#1,处理更多请求。

  3. 每当请求处理器阻止I / O时,服务器都会检查是否有任何请求完成。它以FIFO顺序处理那些以响应客户端,然后继续像以前一样处理。

  4. 这是不是意味着在#2处应该有堆栈溢出,如果有太多请求开始相互阻塞而且没有一个完成?为什么/为什么不呢?

3 个答案:

答案 0 :(得分:4)

node.js通过使用异步技术无处不在 1 来防止您描述的堆栈过度增长。

任何可阻止的东西都会使用回调进行进一步处理,而不是阻止调用。这样可以完全避免堆栈增长,并且可以轻松地重新进入事件循环(“驱动”底层实际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);
}
只要fun可以使用关联的回调对读取I / O进行排队,就会完成

readfun的堆栈在没有阻塞的情况下被重绕 - 它会立即返回到事件循环而没有任何线程堆栈剩余。

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

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

有关详情,请查看node.js 'about'页面,以及最后链接的第一组幻灯片。

1 好吧,差不多我想


您在评论中提到QueueUserAPC。使用这种类型的处理,允许排队的APC进行阻塞,并且队列中的下一个APC在线程的堆栈上处理,使其成为“递归”调度。

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

初始状态:

Queue   ABC
Stack   xxxxxxxx

线程休眠,因此APC调度开始,进入A:

的处理
Queue   BC
Stack   AAAAxxxxxxxx

一个块,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')(可能是一个上下文 - 简单地说是一个包含所有变量的哈希):

I/O queue  A'
Queue      BC
Stack      AAAAxxxxxxxx

然后A 返回,然后又回到:

I/O queue  A'
Queue      BC
Stack      xxxxxxxx

注意:不再是堆栈帧。 I / O挂起队列实际上由操作系统管理(使用epollkqueue或其他)。主线程检查事件循环中的OS I / O就绪状态和待处理(需要CPU)队列。

然后B得到一些CPU:

I/O queue  A'
Queue      C
Stack      BBBBBBBxxxxxxxx

同样的事情,B想做I / O.它将新回调排队,返回

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

如果B的I / O请求同时完成,则下一个快照可能看起来像

I/O queue  A'
Queue      B'
Stack      CCCCCxxxxxxxx

处理线程上没有多个回调堆栈帧。 API不提供阻止调用,该堆栈没有表现出APC模式的递归增长类型。

答案 1 :(得分:2)

node.js基于谷歌的V8 JavaScript引擎,该引擎使用了一个事件循环。

答案 2 :(得分:0)

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

在Node.js中,运行时不会在中间中断您的函数以开始执行另一个函数。相反,您必须返回,然后才能启动Node.js并发。

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

节点在此示例中,创建了一个回调闭包并调用了readItem。据推测,readItem将排队发出查询并设置自己的内部回调,以便在查询结果准备好时执行。因此,此示例函数readAndWriteItem仅将要通过网络发送的消息排队,设置更多回调,并立即返回。一旦这个函数返回,Node.js就可以运行它的事件循环魔法。

由于函数已经返回,并且因为当你使用Node.js时这是全面的情况,所以没有堆栈溢出。