2

我的理解是(非集群)nodejs 程序中的所有代码都在同一个线程中运行。鉴于此,我希望所有此类代码都将作为同一根事件循环的子代码运行,因此如果检查堆栈跟踪以查找在不同回调中运行的代码,我们最终仍会回溯到同一条目(该事件循环的“调度事件”行)。但事实并非如此,我不明白为什么。

考虑以下:

function printStackTrace() {
    console.log(new Error().stack);
}

printStackTrace();
setTimeout(printStackTrace, 1000);

运行产生:

Error
    at printStackTrace (/tmp/node/test.js:4:17)
    at Object.<anonymous> (/tmp/node/test.js:7:1)
    at Module._compile (module.js:446:26)
    at Object..js (module.js:464:10)
    at Module.load (module.js:353:32)
    at Function._load (module.js:311:12)
    at Array.0 (module.js:484:10)
    at EventEmitter._tickCallback (node.js:190:39)
Error
    at Object.printStackTrace [as _onTimeout] (/tmp/node/test.js:4:17)
    at Timer.ontimeout (timers.js:94:19)

并且简单地console.log(new Error().stack);在 REPL 中运行仍然会给出不同的根:

Error
    at repl:1:13
    at REPLServer.eval (repl.js:80:21)
    at repl.js:190:20
    at REPLServer.eval (repl.js:87:5)
    at Interface.<anonymous> (repl.js:182:12)
    at Interface.emit (events.js:67:17)
    at Interface._onLine (readline.js:162:10)
    at Interface._line (readline.js:426:8)
    at Interface._ttyWrite (readline.js:603:14)
    at ReadStream.<anonymous> (readline.js:82:12)

因此,每个项目的根(最底部)项目是不同的(分别在 EventEmitter、Timer 和 ReadStream 中)。其他回调(例如 net)也是如此。

所以我想要么

  • 事件循环是本机 (C++) 代码,因此它不会显示在堆栈跟踪中,并且异步服务的基本提供者(repl.js、timers.js 等)使用本机 v8 api 调用向它注册自己.
  • 事件循环是 JavaScript,但Error()有特殊的代码来隐藏它(作为不必要的实现细节)

其中哪一个(如果有的话)是这种情况,一般来说,我可以在 nodejs(编辑:或 v8)源中的哪个位置读取真正的根事件循环的实现?

4

1 回答 1

1

答案(或至少是一个线索)就在堆栈跟踪中。只需按照堆栈底部文件中的代码进行操作即可。

我不确定您使用的是什么版本的节点(0.6?更新时间!),但在最新的(0.10.17)中,

setTimeout(function() { console.log(new Error().stack) }, 1);

打印出来:

Error
    at null._onTimeout (repl:1:38)
    at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)

所以让我们去timers.js:110。这条线位于函数内部,该listOnTimeout函数被分配给实例的ontimeout属性。Timer

A是一个与libuv接口Timer的 C++ 模块调用该函数的是 C++ 代码。ontimeout

所以这就是你的答案:堆栈的根是由 C++ 代码调用的 JavaScript 函数(无论是计时器还是流管道)。

提供给您的堆栈跟踪Error不会向您显示任何涉及调用函数的本机代码。事件循环本身是由 V8(本机代码)而不是 JavaScript 实现的,因此您看不到超出该边界的任何内容应该是有意义的。

所以发生的事情非常接近你的第一个猜测。JavaScript 代码通过为函数设置一些属性(或在调用本机代码时将函数作为参数传递)来注册回调。当 C++ 想要调用该函数时,它会获取引用并指示 V8 通过调用v8::Function::Call.

如果您对 V8 的工作原理感兴趣,那么嵌入器指南是一个好的开始。

于 2013-08-22T20:22:57.727 回答