【问题标题】:How does the JavaScript heap handle recursionJavaScript 堆如何处理递归
【发布时间】:2021-12-10 01:12:32
【问题描述】:

您好,我上面的问题有点含糊,所以我会尽量说得更清楚。

我有以下形式的代码:

function main () {

  async function recursive () {
     var a = "Hello World";
     var b = "Goodbye World";

     recursive();

  }
  recursive();

}

我遇到了堆内存不足的问题。

假设我上面展示的是我的程序的行为方式,在递归函数中声明了 a 和 b,我的问题是变量是否在递归函数中调用 recursive 时被销毁,或者它们是否会持续存在直到存在假设我让 main 函数运行足够长的时间以使其发生,不再进行递归调用,并且 main 函数到达其端点。

我担心它们在堆中仍然存在,因为我的真实程序在这些变量中存储了大字符串,我担心这就是我用完堆的原因。

【问题讨论】:

  • 您缺少await recursive(),。从理论上讲,这应该会阻止堆耗尽。但是今天有一个类似的问题表明 Chrome 至少优化了 async.. 虽然做 await Promise.resolve() 可以防止这种情况-> 看到这个线程-> stackoverflow.com/questions/51554274/…
  • 我需要调用递归来并行运行,这就是我不等待的原因。无论如何,等待不会导致未等待的承诺进入堆?
  • 如果您不等待至少某种形式的限制,那么堆当然会填满,..the un awaited promise to go to the heap anyway? 确实,这就是您的问题所在.. 您将承诺无限推到堆,你需要某种形式的节流。
  • 很差。它没有 tco

标签: javascript node.js asynchronous recursion heap-memory


【解决方案1】:

JavaScript 还没有(?)对递归进行尾调用优化,所以可以肯定你最终会填满你的调用堆栈。您的堆被填满的原因是因为recursive 被定义为async 函数并且从不等待分配的Promise 对象解析。这将填满您的,因为垃圾收集器没有机会收集它们。

简而言之,递归不会用完堆,除非您在函数内分配堆中的东西。

正在发生的事情的扩展:

main
  new Promise(() => {
    new Promise(() => {
      new Promise(() => {
        new Promise(() => {
          new Promise(() => { //etc.

您说您不等待 Promise 是因为您希望事情并行运行。由于几个原因,这是一种危险的方法。应始终等待 Promise 以防它们抛出,当然您要确保分配可预测的内存使用量。如果此递归函数意味着您获得了不可预测的所需任务数量,则应考虑使用任务队列模式:

const tasks = [];

async function recursive() {
  // ...
  tasks.push(workItem);
  // ...
}

async function processTasks() {
  while (tasks.length) {
    workItem = tasks.unshift();

    // Process work item. May provoke calls to `recursive`
    // ...do awaits here for the async parts
  }
}

async function processWork(maxParallelism) {
  const workers = [];
  for (let i = 0; i < maxParallelism; i++) {
    workers.push(processTasks());
  }

  await Promise.all(workers);
}

最后,以防万一,async 函数不允许您进行并行处理;它们只是提供了一种更方便地编写 Promises 并执行顺序和/或并行等待异步事件的方法。 JavaScript 仍然是一个单线程执行引擎,除非你使用 web worker 之类的东西。因此,使用异步函数尝试并行化 同步 任务不会为您做任何事情。

【讨论】:

  • 即使我不等待,调用 recursive() 是否会在堆上做出承诺?如果是这种情况,它什么时候会离开堆?也感谢您的快速回复
  • 异步函数开始同步执行,直到等待或返回某些东西时才会返回 Promise。你有一个同步无限递归循环,它只是在 Promise 之后分配 Promise。你有没有真正想做的事情?使用 Promises 通常不会有任何问题,但你肯定不会做一些正常的事情,除非你只是四处游荡看看会发生什么。
  • 是的,它每次递归调用都会做一些事情。它所做的事情我不能说,但我可以说它正确地完成了几百次迭代,然后由于堆大小不足而崩溃。递归调用还旨在获取更多要递归的项目
  • 我怀疑您需要您的代码示例来反映更接近实际所做的事情。我们可能有更实际的建议。如果您的调用模式更正常并且您的堆正在填满,您可能需要考虑制作更像一个任务队列并使用固定大小的工作池。
  • 添加了希望对您的并行工作人员进行封顶的有用示例。
猜你喜欢
  • 2014-05-23
  • 1970-01-01
  • 2019-02-01
  • 1970-01-01
  • 2014-09-26
  • 2012-05-02
  • 2017-03-26
  • 1970-01-01
  • 2017-09-06
相关资源
最近更新 更多