Javascript 不应该';Node.js中的异步编程是否会导致堆栈溢出?

Javascript 不应该';Node.js中的异步编程是否会导致堆栈溢出?,javascript,node.js,Javascript,Node.js,我刚刚意识到(单线程)Node.js有一个问题: 服务器开始响应请求,请求一直运行,直到由于I/O阻塞为止 当请求处理器阻塞时,服务器启动并返回到步骤1,处理更多请求 每当请求处理器阻塞I/O时,服务器都会检查任何请求是否已完成。它以FIFO的方式处理这些数据以响应客户机,然后像以前一样继续处理 这难道不意味着如果太多的请求开始相互阻塞,而没有一个请求完成,那么#2处应该会出现堆栈溢出吗?为什么/为什么不?node.js基于谷歌的V8 JavaScript引擎,该引擎使用事件循环 看 n

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

  • 服务器开始响应请求,请求一直运行,直到由于I/O阻塞为止

  • 当请求处理器阻塞时,服务器启动并返回到步骤1,处理更多请求

  • 每当请求处理器阻塞I/O时,服务器都会检查任何请求是否已完成。它以FIFO的方式处理这些数据以响应客户机,然后像以前一样继续处理


  • 这难道不意味着如果太多的请求开始相互阻塞,而没有一个请求完成,那么#2处应该会出现堆栈溢出吗?为什么/为什么不?

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


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


    node.js
    通过在任何地方使用异步技术来防止您描述的堆栈过度增长1

    任何可能阻塞的操作都会使用回调进行进一步处理,而不是阻塞调用。这完全避免了堆栈增长,并使重新进入事件循环变得容易(它“驱动”了底层的真实I/O和请求调度)

    考虑以下伪代码:

    fun() {
      string = net.read();
      processing(string);
    }
    
    线程在读取时被阻塞,堆栈只能在两次读取都完成并且
    处理完成后释放

    现在,如果您的所有代码如下所示:

    fun() {
      net.read(onDone: processing(read_data));
    }
    
    如果您实现了
    ,请阅读以下内容:

    net.read(callback) {
      iorequest = { read, callback };
      io.push_back(iorequest);
    }
    
    fun
    只要
    read
    可以将读I/O与相关回调一起排队,就会完成
    fun
    的堆栈在没有阻塞的情况下被重绕-它“立即”返回到事件循环,没有任何线程堆栈剩余

    也就是说,您可以转到下一个回调(重新进入事件循环),而无需在线程堆栈上保留任何每个请求的数据

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

    有关这方面的更多信息,请查看
    node.js
    页面以及最后链接的第一组幻灯片

    我想差不多吧


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

    假设我们有三个APC待处理(
    A
    B
    C
    )。我们得到:

    初始状态:

    Queue   ABC
    Stack   xxxxxxxx
    
    线程休眠,因此APC调度开始,进入处理:

    Queue   BC
    Stack   AAAAxxxxxxxx
    
    A块,B在同一堆栈上调度:

    B块,C被调度:

    Queue   
    Stack   CCCCCCCBBBBBBAAAAxxxxxxxx
    
    很明显,如果有足够多的阻塞APC挂起,堆栈最终会爆炸

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

    Queue      ABC
    Stack      xxxxxxxx
    
    A开始处理:

    Queue      BC
    Stack      AAAAxxxxxxxx
    
    现在A需要做一些阻塞的事情——在
    node.js
    中,它实际上不能。它所做的是排队等待另一个请求(
    A'
    )(可能是一个上下文-简单地说是一个包含所有变量的哈希):

    然后A返回并返回到:

    I/O queue  A'
    Queue      BC
    Stack      xxxxxxxx
    
    注意:不再使用堆叠框架。I/O挂起队列实际上是由操作系统管理的(使用
    epoll
    kqueue
    或其他方法)。主线程检查事件循环中的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模式所表现出的递归增长类型。

    node.js
    通过在任何地方使用异步技术来防止所描述的堆栈过度增长1

    任何可能阻塞的操作都会使用回调进行进一步处理,而不是阻塞调用。这完全避免了堆栈增长,并使重新进入事件循环变得容易(它“驱动”了底层的真实I/O和请求调度)

    考虑以下伪代码:

    fun() {
      string = net.read();
      processing(string);
    }
    
    线程在读取时被阻塞,堆栈只能在两次读取都完成并且
    处理完成后释放

    现在,如果您的所有代码如下所示:

    fun() {
      net.read(onDone: processing(read_data));
    }
    
    如果您实现了
    ,请阅读以下内容:

    net.read(callback) {
      iorequest = { read, callback };
      io.push_back(iorequest);
    }
    
    fun
    只要
    read
    可以将读I/O与相关回调一起排队,就会完成
    fun
    的堆栈在没有阻塞的情况下被重绕-它“立即”返回到事件循环,没有任何线程堆栈剩余

    也就是说,您可以转到下一个回调(重新进入事件循环),而无需在线程堆栈上保留任何每个请求的数据

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

    有关这方面的更多信息,请查看
    node.js
    页面以及最后链接的第一组幻灯片

    我想差不多吧


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

    假设我们有三个APC待处理(
    A
    B
    C
    )。我们得到:

    初始状态:

    Queue   ABC
    Stack   xxxxxxxx
    
    线程休眠,因此APC调度开始,进入处理:

    Queue   BC
    Stack   AAAAxxxxxxxx
    
    A块,B在同一堆栈上调度:

    B块,C被调度:

    Queue   
    Stack   CCCCCCCBBBBBBAAAAxxxxxxxx
    
    很明显,如果有足够多的阻塞APC挂起,堆栈最终会爆炸

    使用
    node.js
    ,请求不会被分配