简体   繁体   English

Node.js中的异步编程是否应该引起StackOverflow?

[英]Shouldn't asynchronous programming in Node.js cause a StackOverflow?

I just realized a problem with the (single-threaded) Node.js: 我刚刚意识到(单线程)Node.js的问题:

  1. The server begins responding to a request, and the request runs until it blocks because of I/O. 服务器开始响应请求,该请求一直运行到由于I / O而阻塞为止。

  2. When the request processor blocks, the server kicks in and goes back to step #1, processing more requests. 当请求处理器阻塞时,服务器启动并返回到步骤#1,处理更多请求。

  3. Whenever a request processor blocks for I/O, the server checks to see if any request is finished. 每当请求处理器阻塞I / O时,服务器都会检查是否有任何请求完成。 It processes those in FIFO order to respond to clients, then continues processing as before. 它以FIFO顺序处理那些以响应客户端,然后像以前一样继续处理。

Doesn't that mean that there should be a stack overflow at #2, if too many requests start blocking each other and none of them finishes? 难道这意味着如果太多请求开始互相阻塞并且没有一个完成,那么#2应该有堆栈溢出? Why/why not? 为什么/为什么不呢?

node.js prevents the stack overgrowth you describe by using asynchronous techniques everywhere 1 . node.js通过在各处使用异步技术来防止您描述的堆栈过度增长1

Anything that could block uses callbacks for further processing, not blocking calls. 任何可能阻止的操作都将使用回调进行进一步处理,而不是阻止调用。 This avoids stack growth completely, and makes it easy to re-enter the event loop (that "drives" the underlying real I/O and request dispatching). 这样可以完全避免堆栈增长,并使重新输入事件循环(“驱动”底层实际I / O和请求分派)变得容易。

Consider this pseudo-code: 考虑以下伪代码:

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

Thread is blocked on read, stack can only be free'd up after both the read completes, and processing is done. 线程在读取时被阻止,只有在读取和processing都完成后才能释放堆栈。

Now if all your code is like: 现在,如果您所有的代码都像这样:

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

And if you implement read like this: 而如果要实现read这样的:

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

fun is done as soon as read can queue a read I/O with the associated callback. 只要read可以将读取的I / O与关联的回调一起排队,就可以完成fun fun 's stack is rewound without blocking - it returns "immediately" to the event loop without any thread stack leftovers. fun的堆栈无阻塞地倒回-它“立即”返回事件循环,而没有任何线程堆栈剩余。

Ie you can move on to the next callback (re-enter the event loop) without keeping any per-request data on the thread stack. 也就是说,您可以继续下一个回调(重新进入事件循环),而不必在线程堆栈上保留任何按请求的数据。

So node.js avoid stack overgrowth by using asynchronous callbacks wherever blocking calls would happen in "user" code. 因此, node.js通过在“用户”代码中发生阻塞调用的任何地方使用异步回调来避免堆栈过度增长。

For more about this, please check out the node.js 'about' page, and the first set of slides linked at the end. 有关此内容的更多信息,请查看node.js “关于”页面,最后链接第一组幻灯片。

1 well, nearly I guess 1 好,我猜差不多


You mention QueueUserAPC in a comment. 您在评论中提到QueueUserAPC With that type of processing, a queued APC is allowed to block, and the next APC in the queue gets processed on the thread's stack , making it a "recursive" dispatch. 使用这种类型的处理,允许排队的APC阻塞,并且队列中的下一个APC在线程的堆栈上得到处理,从而使其成为“递归”调度。

Say we have three APCs pending ( A , B and C ). 假设我们有三个待处理的APC( ABC )。 We get: 我们得到:

Initial state: 初始状态:

Queue   ABC
Stack   xxxxxxxx

Thread sleeps so APC dispatch starts, enters processing for A: 线程休眠,因此APC调度开始,进入A的处理:

Queue   BC
Stack   AAAAxxxxxxxx

A blocks, B is dispatched on the same stack : A块,B分配在同一堆栈上

Queue   C
Stack   BBBBBBAAAAxxxxxxxx

B blocks, C is dispatched: B块,C调度:

Queue   
Stack   CCCCCCCBBBBBBAAAAxxxxxxxx

It's clearly visible that if enough blocking APCs are pending, the stack will eventually blow up. 显而易见的是,如果有足够多的阻塞APC待处理,堆栈最终将崩溃。

With node.js , the requests are not allowed to block . 使用node.js不允许阻止请求。 Instead, here's a mock-up of what would happen for the same three requests: 相反,这是对这三个请求将发生的情况的模拟:

Queue      ABC
Stack      xxxxxxxx

A starts processing: A开始处理:

Queue      BC
Stack      AAAAxxxxxxxx

Now A needs to do something that blocks - in node.js , it actually can't . 现在需要做的事,阻止-在node.js ,但实际上做不到 What it does is queue another request ( A' ) (presumably with a context - simplistically a hash with all your variables): 它的作用是将另一个请求( A' )排队(大概带有上下文-简单来说就是带有所有变量的哈希):

I/O queue  A'
Queue      BC
Stack      AAAAxxxxxxxx

Then A returns and were's back to: 然后A 返回并返回到:

I/O queue  A'
Queue      BC
Stack      xxxxxxxx

Notice: no more A stackframe. 注意:不再是一个堆栈框架。 The I/O pending queue is actually managed by the OS (using epoll or kqueue or whatever). I / O等待队列实际上是由操作系统管理的(使用epollkqueue或其他方法)。 The main thread checks both the OS I/O ready states and pending (needing CPU) queues in the event loop. 主线程检查事件循环中OS I / O就绪状态和挂起(需要CPU)队列。

Then B gets some CPU: 然后,B获得了一些CPU:

I/O queue  A'
Queue      C
Stack      BBBBBBBxxxxxxxx

Same thing, B wants to do I/O. 同样,B想要执行I / O。 It queues a new callback and returns . 它排队一个新的回调并返回

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

If B's I/O request completes in the mean time, the next snapshot could look like 如果B的I / O请求在此期间完成,则下一个快照可能看起来像

I/O queue  A'
Queue      B'
Stack      CCCCCxxxxxxxx

At no point is there more than one callback stack frame on the processing thread. 在任何时候,处理线程上都不会超过一个回调堆栈框架。 Blocking calls are not provided by the API, that stack doesn't exhibit the type of recursive growth the APC pattern does. API没有提供阻塞调用,该堆栈没有展现出APC模式所具有的递归增长类型。

There are a few key aspects of working with the event loop in Node.js that are different from working with threads. 与使用线程不同,在Node.js中使用事件循环有几个关键方面。

In Node.js, the runtime does not interrupt your function in the middle to start executing another function. 在Node.js中,运行时不会在中间中断您的函数以开始执行另一个函数。 Instead, you must return from the current function before the Node.js concurrency will kick in. 相反,您必须从当前函数返回然后才能启动Node.js并发。

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

Node that in this example, a callback closure is created and readItem is called. 在此示例中,创建回调闭包并readItem Presumably, readItem will queue up the issuing of a query and set up its own internal callback to be executed when the result of the query is ready. 大概, readItem将使查询的发出排队,并设置自己的内部回调,以在查询结果准备好时执行。 So this example function readAndWriteItem merely queues up a message to be sent across the wire, sets up some more callbacks, and immediately returns . 因此,此示例函数readAndWriteItem仅将要通过网络发送的消息排队,设置更多的回调, 然后立即返回 Once this function returns, Node.js can work its event loop magic. 此函数返回后,Node.js即可发挥其事件循环的作用。

Since the function has returned , and since this is the case across the board when you use Node.js, there is no stack overlow. 由于该函数已返回 ,并且在使用Node.js时都是这种情况,因此不会出现堆栈溢出问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM