递归 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 使堆栈增长的主要内容,如果未能解决你的问题,请参考以下文章