Javascript/Node 中从不执行用户代码的隐藏线程:是否可能,如果可能,是否会导致竞争条件的神秘可能性?

IT技术 javascript node.js callback race-condition node-request
2021-03-08 14:34:33

根据评论/答案,请参阅问题底部的更新:这个问题实际上是关于不执行回调隐藏线程的可能性


我有一个关于涉及节点请求module的潜在神秘场景的问题,其中:

  • 一个完整的 HTTP 请求被构建并通过网络执行(需要多少毫秒甚至秒)

  • ...本地机器上运行时执行单个函数之前(通常以纳秒为单位?) - 请参阅下文了解详细信息

我发布这篇文章主要是为了检查完整性,以确保我没有误解 Node/JS/Request module代码。

请求module中示例(请参阅该部分中的第二个示例),是这样的:

// Copied-and-pasted from the second example in the 
// Node Request library documentation, here:
// https://www.npmjs.com/package/request#examples

// ... My ARCANE SCENARIO is injected in the middle

var request = require('request')
  request(
    { method: 'GET'
    , uri: 'http://www.google.com'
    , gzip: true
    }
  , function (error, response, body) {
      // body is the decompressed response body 
      console.log('server encoded the data as: ' + (response.headers['content-encoding'] || 'identity'))
      console.log('the decoded data is: ' + body)
    }
  )

    // **************************************************** //
    // Is the following scenario possible?
    //
    // <-- HANG HANG HANG HANG HANG HANG HANG HANG HANG -->
    //
    // Let us pretend that the current thread HANGS here,
    // but that the request had time to be sent,
    // and the response is pending being received by the thread
    //
    // <-- HANG HANG HANG HANG HANG HANG HANG HANG HANG -->
    // **************************************************** //

.on('data', function(data) {
    // decompressed data as it is received 
    console.log('decoded chunk: ' + data)
  })
  .on('response', function(response) {
    // unmodified http.IncomingMessage object 
    response.on('data', function(data) {
      // compressed data as it is received 
      console.log('received ' + data.length + ' bytes of compressed data')
    })
  })

我已经在代码片段中指出了我的神秘场景。

假设 Node 进程在指定的点挂起,但是 Node 在内部(在一个隐藏线程中,对 Javascript 不可见,因此不调用任何回调)能够构造请求,并通过网络发送它;假设挂起一直持续到接收到一个响应(比如两个块)并等待 Node.js 处理。(这种情况肯定是很神秘的,我不确定理论上是否可行。)

然后假设挂起结束,上面的 Node 线程被唤醒。此外,假设(以某种方式)Node 能够一直处理响应,直到执行上述代码中的回调函数为止(但没有越过原始代码路径中代码中的“挂起”点——再次,如果这在理论上是可能的)。

上述神秘场景在理论上是可能的吗?如果是这样,在'data'事件被安排在对象上之前,数据包不会通过网络接收并组合,准备传递给回调函数吗?在这种情况下,如果可能的话,我想这个'data'事件会被错过。

同样,我明白这是一个神秘的场景——考虑到所涉及的内部机制和编码,它甚至在理论上是不可能的。

那是我的问题 - 上面的神秘场景及其极不可能的竞争条件在理论上是可能的吗?

我问只是为了确保我没有遗漏一些关键点。谢谢。


更新:来自评论和答案:我现在已经澄清了我的问题。“神秘场景”需要有一个 HIDDEN 线程(因此不能执行任何用户代码,包括 CALLBACKS)来构造请求,通过网络发送它并接收响应 - 没有任何回调要触发,包括'data'回调 - 并在'response'回调准备好被调用时停止,等待(单个)可见 JS 线程唤醒。

3个回答

不,这不可能发生。

是的,确实有“隐藏的”后台线程为异步方法工作,但那些不调用 callbacksjavascript 的所有执行都同步、顺序地发生在同一个线程上。data事件回调将始终异步执行,即在当前脚本/函数运行完成之后。

虽然在回调创建并附加到事件发射器之前确实可能已经有来自网络的数据包到达,但在发送请求之前总是创建侦听最低级别数据包的回调 - 它是本机“makeRequest”的一个参数" 方法,并且可以从一开始就被调用。所以当一个数据包在当前脚本(仍然被构建事件发射器和附加处理程序占用)完成之前到达时,这个事件被排队,并且回调只会在事件循环准备好后执行 - 在下一轮。到那时,data事件回调肯定是创建和附加的。

有点误导。当涉及到网络 I/O 时,没有额外的线程。
2021-04-20 14:34:33
@slebetman:没错,它是异步的,即使在较低级别也是如此。我只是试图简化并将其与文件 IO 等放在同一个锅中。
2021-04-23 14:34:33
伟大的!这回答了我的问题 - 特别是您的评论the callback that listens for packets on the lowest level is always created before the request is sent - it is an argument to the native "makeRequest" method因为如果确实存在需要在事件上触发的内部Javascript回调'data',即使Request在上面的示例代码中没有在事件上设置它,也可以保证停止任何可能的“隐藏”线程。如果您有时间并且很容易 - 您是否有指向此功能的参考链接?
2021-05-05 14:34:33
你可以通过挖掘自己的源httpmodule:-)你会发现线,如process.binding('http_parser')这里由本机代码出口)这里
2021-05-11 14:34:33

nodejs Javsacript 执行是单线程和事件驱动的。这意味着一切都通过事件队列运行。一个 Javascript 执行线程运行直到它完成,然后系统检查事件队列以查看是否还有其他事情要做(等待触发的计时器、等待调用的异步回调等......)。

nodejs 在其某些实现中确实使用了一些内部线程(例如文件 I/O),但我的理解是它在网络中不使用线程。但是,是否有一些内部线程并不重要,因为网络等子系统和主 nodejs JS 线程之间的所有通信都是通过事件队列进行的。

nodejs 执行线程永远不会被中断来做其他事情。它完成并运行到完成,然后 JS 引擎检查事件队列以查看是否还有其他内容等待执行。

当套接字上有可用的传入数据时,一个事件被放置在事件队列中。当前正在执行的 nodejs Javascript 完成它正在执行的操作,然后 JS 引擎看到事件队列中有一个事件并触发该事件。如果有一个与该事件关联的函数回调或事件处理程序(通常有),那么它会被调用以执行该事件。

如果某些基础设施(例如网络)的内部出现故障,那么 nodejs 代码所发生的一切就是某些网络事件不会发生。nodejs 代码有它的事件处理程序,并且在基础设施解开并创建事件之前不会接收他们正在等待的事件。这不会在 nodejs 代码中创建任何类型的挂起。

因此,在您的更新中:

来自评论和答案:我现在已经澄清了我的问题。“神秘场景”需要有一个 HIDDEN 线程(因此不能执行任何用户代码,包括 CALLBACKS)来构造请求,通过网络发送它并接收响应 - 没有任何回调要触发,包括'data' 回调 - 并在准备好调用 'response' 回调时停止,等待(单个)可见 JS 线程唤醒。

nodejs 线程运行完成,然后 JS 引擎等待新事件发生(例如,将其放入事件队列)。当该事件发生时,JS 引擎运行与该事件对应的代码(事件处理程序、回调等...)。你让它听起来像是单个可见的 JS 线程在睡觉等待唤醒,它可能会卡在那里,因为其他一些子系统被挂起。事实并非如此。唯一可能发生的事情是单个 JS 线程具有事件处理程序的某些事件永远不会发生。这与您向服务器发送消息并且您有一个事件处理程序来查看响应,但服务器从不发送响应的情况没有什么不同。您的 nodejs 代码继续处理其他事件(计时器、其他网络、其他 I/O),但是这个特定的事件永远不会发生,因为另一台服务器从来没有发送过会触发该事件的数据。什么都没有。

这是“事件 I/O”,这就是 nodejs 描述自己的方式。

@DanNissenbaum - “处理事件”是什么意思?如果没有 nodejs 代码来处理事件,那么当事件发生时就无事可做——它只会被扔掉。这可能发生。您可以为将来发生的某些事件注册一个事件侦听器,然后您可以在事件发生之前删除该事件侦听器。该事件将会发生,并且不会在发生时注册任何 JS 代码来执行任何操作。在这种情况下,事件是否真的进入事件队列并不重要 - 无论哪种方式,结果都是一样的。
2021-05-03 14:34:33
谢谢。但是考虑一下If there's a function callback or event handler associated with that event (there usually is) ...——如果没有呢?是否可能有适当的优化,以便在这种情况下隐藏的后台线程将处理事件 - 而不是主 JS 线程 - 因为不需要执行 Javascript 代码,因此不会违反“单个可见线程”规则(即,没有回调未决且没有回调执行)?
2021-05-19 14:34:33

Node.js 中只有一个线程;事件循环用于处理异步运行的任务,任何排队的任务都不会中断任何已经运行的任务。所以不,那里没有竞争条件。

@DanNissenbaum:当涉及到网络 I/O 时,没有额外的线程,无论是否可见。执行 javascript 和处理网络请求都是单线程——它们轮流进行。唯一的例外是磁盘 I/O,节点开发人员选择将其实现为线程以简化对异步磁盘 I/O 的跨平台支持
2021-04-30 14:34:33
@DanNissenbaum:有关节点如何工作的解释,请参见此处:stackoverflow.com/questions/29883525/...
2021-05-01 14:34:33
node.js 内部还有其他线程可以使某些事情正常工作。事实上,异步文件 I/O 使用线程。但是,我的理解是网络不使用线程,它使用事件。
2021-05-06 14:34:33
在 Node 实现内部,我认为必须有额外的线程 - 至少这样一些线程可以阻塞操作系统的事件(或条件等)对象。这些线程对 Javascript/Node 代码不可见。但是 - 只有一个线程执行Javascript代码,对吗?如果是这样 - HTTP 请求的构造是发生在这个 Javascript 线程内,还是发生在(隐藏)线程内(在 Node 内部)?
2021-05-12 14:34:33
@DanNissenbaum:不仅如此,还可以有 UI 事件(浏览器)和线程/作业完成事件(网络工作者)。但是所有这些都可以使用文件/套接字 I/O 作为通信介质来实现,因此系统的核心仍然可以工作。
2021-05-17 14:34:33