为什么Node.JS中的函数和回调都是非阻塞的?

时间:2012-02-20 14:35:04

标签: node.js nonblocking

对Node的新手理解是,如果我重写同步或内联代码来利用函数/回调,我可以确保我的代码是非阻塞的。我很好奇这是如何在事件堆栈方面工作的。这里的简单示例:Don't understand the callback - Stackoverflow是这将阻止:

var post = db.query("select * from posts where id = 1");
doSomethingWithPost(post)
doSomethingElse();

虽然这不会:

callback = function(post){
doSomethingWithPost(post)
}

db.query("select * from posts where id = 1",callback);
doSomethingElse();

好的,我知道我们应该使用回调。但就事件堆栈而言,为什么这样做呢? Javascript是单线程..在第一个示例行中,一个使用昂贵的阻塞I / O操作。在第一行完成之前,第2行无法执行。这是因为第2行需要第1行的信息吗?或者是因为I / O事件只是从根本上阻止了操作,这意味着它们会抓住控制权并且在完成之前不会将其恢复...

在第二个例子中,昂贵的I / O已被移动到一个函数中,我们现在有了一个回调函数。当然,在完成I / O之前,回调不能执行。这不会改变。因此,执行1到2之间所需的时间差异必须主要是第二个请求到达服务器时会发生的情况。

如果第二个请求遇到示例1,它将无法处理,直到请求1由于阻塞操作而完成..但在示例二中..将操作移动到函数中会自动生成子进程或充当多线程?如果Javscript是单线程的,那么除非有某种方式进行并行处理,否则这仍然会造成问题。如果我们使用非阻塞技术(如子进程等),函数/回调是否仅保证是非阻塞的。 。

4 个答案:

答案 0 :(得分:19)

想象一下,你正在一家面包店经营收银机。您按顺序和同步方式处理客户,如下所示:

  1. 接受订单
  2. 告诉面包师烤面包
  3. 等到面包烤好后
  4. 收钱
  5. 送面包
  6. GOTO 1 - 下一位客户
  7. 这将非常缓慢。现在,请尝试按顺序接受订单,但异步处理客户:

    1. 接受订单
    2. 告诉面包师烤面包,完成后通知你。收到通知时:
      1. 收钱
      2. 送面包
    3. GOTO 1 - 下一位客户
    4. UPDATE:我重构了上述内容,因此它更像是一个回调。您,收银员,在将订单发给面包师后会立即点击第3步。当面包师通知您面包已准备好时,您将点击步骤2.1。

      通过这种方式,您仍然可以提供尽可能多的面包 - 您只能出售面包烘焙所需的面包。但是你可以以更有效的方式与客户打交道,因为你不是懒得等待订单回来,而是开始处理下一个客户。

      现在,你可以对此采取各种各样的想法,并预先收取款项,并告诉顾客拿起桌子另一端的面包,或类似的东西。我认为星巴克以这种方式“非常”。收银员接受订单,发出一些要求的东西,并告诉客户等待所有东西都站在取货区域。超高效。

      现在,想象一下你的朋友开始操作另一个收银机。他遵循你的异步示例。您可以更快地处理更多客户!请注意,您唯一要做的就是将您的朋友放在那里并给他你的工作流程。

      您和您的朋友是两个并行运行的单线程事件循环。这类似于两个获取请求的node.js进程。你没有必要做任何复杂的并行化,你只需再运行一个事件循环。

      所以,不,“将操作移动到函数中”不会“自动生成子进程”。它们更类似于警报 - 当它完成时,通知我并让我在此时接听,“这一点”是你回调中的代码。但是回调仍然会在同一个进程和同一个线程中执行。

      现在,node.js还为IO运行内部线程池。这是抽象的远离你:继续面包店比喻,让我们说你有一个面包师的“面包师池” - 对你,站在收银台,你不必知道这一点。你只需给他们订单(“一个酵母面包”),并在收到通知表明它完成后发出订单。但面包师正在他们自己的“面包师池”中平行烘烤他们的面包。

答案 1 :(得分:3)

我的英语不太好,所以我无法理解你的行为是什么意思。 但我可以说'多线程'和'异步'是相似但不同的术语。 即使单个线程可以表示为“异步”。

This document不适用于节点(它用于python异步框架“扭曲”),但可能对您有所帮助。

抱歉我的英语不好。

答案 2 :(得分:0)

接受的答案很棒,但我想补充一些与nonblocking完全相关的内容,特别是问题的这一部分:

  

或者是因为I / O事件只是从根本上阻止了   操作意味着他们抓住控制权并且不回报   直到完成...

即使没有提供自己的IO工作线程池的框架,也可以实现异步IO。实际上,只要底层(操作)系统提供了一些非阻塞IO的机制,它就可以在没有任何多线程的情况下完成。

通常情况下,这可归结为系统调用,如POSIX的select(或微软的version of the same),或者与Linux epoll相同的最新变体。

假设,如果我们在您的示例中看到类似db.query的函数,并假设我们也知道提供该函数的框架依赖于任何多线程,那么它通常可以安全地得出结论:

  • 该框架会跟踪IO descriptors的全局列表以及与已启动的任何非阻止IO请求相关联的回调,例如db.query来电。
  • 框架具有或依赖于某种主应用程序事件循环。这可以是从旧学校while(true)libev
  • 之类的任何内容
  • 在所述主循环的某处,select或类似的函数将用于检查是否有任何待处理的IO请求已完成。
  • 当找到完成的IO请求时,将调用其关联的回调,之后主循环将恢复。

db.query这样的SQL DB调用可能使用网络套接字IO 而不是文件IO ,但是从您的角度来看,作为应用程序开发人员,套接字和文件描述符在许多操作系统上以几乎相同的方式处理,并且无论如何都可以传递给POSIX-like上的select

这通常是单线程,单进程服务器应用程序如何“兼顾”多个同时连接。

答案 3 :(得分:0)

重要提示-db.query()不会阻塞堆栈,因为它是在其他位置执行的。

示例1阻塞,因为第2行需要来自第1行的信息,因此必须等到第1行完成。第3行无法在第2行之前作为按行顺序处理的代码执行,因此第2行会阻塞第3行和其他任何行。

示例2是非阻塞的,因为在db.query完成之前不会调用回调函数来执行,因此它不会阻塞doSomethingElse()。