关于nonblocking:为什么Node.JS中的函数和回调是非阻塞的?

Why is a function and a callback non-blocking in Node.JS?

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

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

虽然这不会:

1
2
3
4
5
6
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由于阻塞操作而完成..但在示例二中..将操作移动到函数中会自动生成子进程或充当多线程?如果Javscript是单线程的,那么除非有某种方式进行并行处理,否则这仍然会造成问题。如果我们使用非阻塞技术(如子进程等),函数/回调是否仅保证是非阻塞的。 。


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

  • 点单
  • 告诉面包师烤面包
  • 等到面包烤好
  • 收钱
  • 提供面包
  • GOTO 1 - 下一位客户
  • 那将是非常缓慢的。现在,尝试按顺序接受订单,但异步处理客户:

  • 点单
  • 告诉面包师烤面包,完成后通知你。收到通知时:

  • 收钱
  • 提供面包
  • GOTO 1 - 下一位客户
  • 更新:我重构了上面的内容,所以它更像是一个回调。您,收银员,在将订单发给面包师后会立即点击第3步。当面包师通知您面包已准备好时,您将点击步骤2.1。

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

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

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

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

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

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


    我的英语不好,所以我不明白你的行为是什么意思。
    但我可以说'多线程'和'异步'是相似但不同的术语。
    即使单个线程可以表示为"异步"。

    本文档不适用于节点(它用于python异步框架"扭曲"),但可能对您有所帮助。

    抱歉我的英语不好。


    重要信息 - db.query()不会阻塞堆栈,因为它在其他地方执行。

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

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


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

    Or is it because I/O events are just fundamentally blocking
    operations meaning that they seize control and don't give it back
    until done...

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

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

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

    • 该框架跟踪与启动的任何非阻塞IO请求相关联的IO描述符和回调的全局列表,如db.query调用。
    • 该框架具有或依赖于某种主要的应用程序事件循环。这可以是从旧学校while(true)到像libev这样的东西
    • 在所述主循环中的某处,将使用select或类似函数来检查是否有任何挂起的IO请求已经完成。
    • 找到完成的IO请求后,将调用其关联的回调,之后主循环将恢复。

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

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