递归 setTimeout 使堆栈增长

Posted

技术标签:

【中文标题】递归 setTimeout 使堆栈增长【英文标题】:Recursive setTimeout makes stack grow 【发布时间】:2020-07-20 08:57:24 【问题描述】:

据我从这两个答案中了解到:

Will a recursive 'setTimeout' function call eventually kill the JS Engine Does use of recursive process.nexttick let other processes or threads work

setTimeout 被递归调用时,堆栈不应增长,但如果您在 FF 或 Chrome 中打开开发工具并运行此函数:

function recur(n = 10) 
  console.trace();
  console.log(n, '++++++++++++++++');
  if (n > 0) setTimeout(() => recur(--n), 1000);


recur()

您将看到以下内容:

console.trace() debugger eval code:2:11
    recur debugger eval code:2
    <anonymous> debugger eval code:8
10 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
9 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
8 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
7 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
6 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
5 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
4 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
3 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
2 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
1 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
0 ++++++++++++++++

这是为什么呢?

【问题讨论】:

@CertainPerformance,你知道我该如何确认吗? 您可以通过观察内存分配来观察这一点。删除调试器的东西并运行recur(9007199254740991) 有和没有setTimeout。由于没有 setTimeout 的内存不足错误(甚至堆栈溢出),您会发现解释器崩溃(如果它是没有内存限制的解释器,您甚至可能会发现您的操作系统崩溃)。使用 setTimeout 只需要很长时间才能执行,但会使用恒定的内存量 也许它正在保存堆栈帧描述以便可以跟踪它们,但不是实际帧,这样GC可以正常发生,但是用户仍然可以调试。 【参考方案1】:

Chrome 开发者工具中的异步堆栈跟踪...旨在让您保持理智,同时尝试找出您的函数为何处于 setTimeout() 递归循环中

https://www.html5rocks.com/en/tutorials/developertools/async-call-stack/

javascript 线程模型是事件循环。 - https://flaviocopes.com/javascript-event-loop/

setTimeout() 在技术上不是递归的。它是一个异步函数调用。在线程内部,它同步返回一个数字 id(您可以在其上调用 clearTimeout()),然后作为副作用将您的函数添加到事件循环堆栈的末尾。它是一个单线程模型,因此一旦 javascript 完成了您当前的线程/事件,它将从堆栈顶部弹出下一个函数以执行。因此 1000 毫秒只是计划执行的最短时间,但如果它仍在执行之前的事件/线程,则可能需要更长的时间。

如果我在节点中运行此代码,我会得到:

node
Welcome to Node.js v12.5.0.
Type ".help" for more information.
> function recur(n = 10) 
...   console.trace();
...   console.log(n, '++++++++++++++++');
...   if (n > 0) setTimeout(() => recur(--n), 1000);
... 
undefined
> 
> recur()
Trace
    at recur (repl:2:11)
    at repl:1:1
    at Script.runInThisContext (vm.js:123:20)
    at REPLServer.defaultEval (repl.js:384:29)
    at bound (domain.js:415:14)
    at REPLServer.runBound [as eval] (domain.js:428:12)
    at REPLServer.onLine (repl.js:700:10)
    at REPLServer.emit (events.js:205:15)
    at REPLServer.EventEmitter.emit (domain.js:471:20)
    at REPLServer.Interface._onLine (readline.js:314:10)
10 ++++++++++++++++
undefined
> Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
9 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
8 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
7 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
6 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
5 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
4 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
3 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
2 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
1 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
0 ++++++++++++++++

【讨论】:

这很棒。谢谢。

以上是关于递归 setTimeout 使堆栈增长的主要内容,如果未能解决你的问题,请参考以下文章

使用 setTimeout 避免堆栈溢出

为啥带有 setTimeout 的函数不会导致堆栈溢出

WPF UserControl:使多个链接的依赖项属性保持同步,而不会导致递归循环 堆栈溢出

递归搜索堆栈,但保持堆栈不变

setTime

调用 setTimeout 会清除调用堆栈吗?