【问题标题】:Will call an async function recursively fill up the stack?会递归地调用异步函数填满堆栈吗?
【发布时间】:2017-04-26 22:00:22
【问题描述】:

我有以下功能。抱歉,它有点长……但实际上并没有太多内容。 这是一个简单的函数,将循环传递消息,并通过 websockets 发送它们。

最关键的部分,也是这个问题的症结,是:这条线会导致堆栈填满吗?

// through this loop
sendMessagesInTab( tabId, cb );

...?基本上,当循环结束时,可能已将更多记录添加到列表中。由于这个函数只会运行一次(那里有一个信号量,currentlyDeliveringTab[ tabId ]),所以当它完成时,我想再次检查列表中没有添加任何内容。为此,我使用了函数本身。

根据我的测试,我实际上并没有设法填满堆栈。我认为那是因为这不是“递归”,因为一切都是异步的。但我很困惑:如果所有时间都添加,这会耗尽所有可用内存吗?

function sendMessagesInTab( tabId, cb ){

  consolelog("Entered sendMessagesInTab for tab", tabId);

  // Semaphore. Only one instance of this is to run at any given time
  if( currentlyDeliveringTab[ tabId ] ){
    consolelog("Already running for tab ", tabId);
    return;
  }
  currentlyDeliveringTab[ tabId ] = true;

  consolelog("Looking up tab...");
  stores.tabs.dbLayer.selectById( tabId, function( err, tab ){
    if( err ){
      delete currentlyDeliveringTab[ tabId ];
      return cb( err );
    }
    if( ! tab ){
      delete currentlyDeliveringTab[ tabId ];
      return new Error("tabId not found!");
    }


    stores.tabMessages.dbLayer.selectByHash( { tabId: tabId }, function( err, tabMessages ){
      if( err ){
        delete currentlyDeliveringTab[ tabId ];
        return cb( err );
      }


      if( !tabMessages.length ){
        consolelog("No messages to be delivered, that's it...");
        delete currentlyDeliveringTab[ tabId ];
        return cb( null );
      }


      /*
      // TESTING RECURSION
      delete currentlyDeliveringTab[ tabId ];
      return sendMessagesInTab( tabId, function(err ) { console.log("ERROR:", err ) } );
      */

      consolelog("There are messages to be delivered:", tabId, tabMessages.length );
      async.eachSeries(

        tabMessages,

        function( record, cb ){

          consolelog("Checking the connection...");

          // If the connection is not there, all good but "false" (delivery failed)
          var ws = connections[ record.tabId ] && connections[ record.tabId ].ws;
          if( ! ws ) return cb( new Error("No websocket connection") ); // End of cycle will kill currentlyDeliveringTab

          var message = record.message;
          message.messageId = record.id;

          // Attempt delivery over websocket. If it works, great. If it doesn't,
          // sorry.
          consolelog("Attempt to stringify the message", tabId);
          try {
            var strMessage = JSON.stringify( message );
          } catch ( err ){
            return cb( err );
          }

          consolelog("Sending message through the websocket", tabId);
          ws.send( strMessage, function( err ){
            if( err ) return cb( err ); // End of cycle will kill currentlyDeliveringTab

            consolelog("Deleting the message", tabId);

            stores.tabMessages.dbLayer.deleteById( record.id, function( err ){
              if( err ) return cb( err ); // End of cycle will kill currentlyDeliveringTab

              consolelog("Updating lastSync", tabId);

              cb( null );
            });
          })
        },

        function( err ){
          if( err ){
            consolelog("ERROR!", err );
            delete currentlyDeliveringTab[ tabId ];
            return cb( err );
          }

          consolelog("All messages have been sent successfully!");
          consolelog("Now running sendMessagesInTab again in case messages were added WHILE sending these");
          delete currentlyDeliveringTab[ tabId ];

          // Rerun sendMessagesInTab to check that messages weren't added while going
          // through this loop
          sendMessagesInTab( tabId, cb );
        }
      );

    });
  });
}

【问题讨论】:

    标签: node.js asynchronous recursion


    【解决方案1】:

    会以递归方式调用异步函数来填满堆栈吗?

    不,如果从异步回调中调用它,它不会导致任何堆栈建立。当您执行异步函数然后将控制权返回给系统时(在等待回调被调用时),当前执行线程已经完成,因此堆栈被清空,然后在完成事件时启动一个全新的堆栈触发异步操作,然后触发您的完成回调。

    请记住,与 C/C++ 之类的语言相比,Javascript 的不同之处在于,函数作用域和函数作用域中的对象是垃圾收集实体,不在堆栈上。因此,您可以在异步回调周围设置一个闭包,即使执行线程已完全展开并清除堆栈,该回调仍处于活动状态。当任何活动代码不再可访问函数范围内的元素时,将对其进行垃圾收集(类似于 Javascript 中的其他对象)。因此,它在异步回调仍然可以发生时保持活动状态,但是一旦该回调发生并且不能再发生,那么范围内的所有内容都可以进行垃圾回收。

    但是,只要调用了异步回调并且回调完成执行,就应该对本地范围进行 GC。除非您继续添加越来越多的仍然存在并且可以调用的事件处理程序,否则获得本地范围的累积将是一种不寻常的情况。如果无法再调用事件处理程序(因为启动它们的内部对象本身已完成),则范围内的项目将无法访问,并且符合 GC 条件。

    【讨论】:

    • 调用者的局部范围内的变量呢?我的意思是,即使它是异步的,变量范围规则仍然适用……当然。还是我只是在这里无所事事?
    • @Merc - 巧合的是,我刚刚在我的答案中添加了一段关于函数范围内变量的内容。
    • 为什么投反对票?如果您认为此答案中的某些内容不正确,请说明您认为不正确的内容!
    • 我赞成平衡反对票,抱歉,我不知道谁或为什么会反对。
    • 回到你的问题:好的,所以它不会影响堆栈,但它可能会影响内存消耗......我可以通过确保所有变量都定义在那个之外(所以没有任何东西被有效分配)尽可能......对吗?
    猜你喜欢
    • 2014-03-19
    • 1970-01-01
    • 1970-01-01
    • 2016-09-21
    • 1970-01-01
    • 2019-10-05
    • 2019-11-14
    • 2023-03-27
    • 1970-01-01
    相关资源
    最近更新 更多