【问题标题】:Why is a function and a callback non-blocking in Node.JS?为什么 Node.JS 中的函数和回调是非阻塞的?
【发布时间】:2012-03-10 21:26:45
【问题描述】:

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 完成之前无法处理.. 但在示例二中.. 将操作移动到函数中会自动生成子进程或充当多线程?如果 JavaScript 是单线程的,这仍然会造成问题,除非有某种方式进行并行处理。如果我们使用子进程等非阻塞技术,函数/回调是否只保证是非阻塞的。 .

【问题讨论】:

    标签: node.js nonblocking


    【解决方案1】:

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

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

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

    【讨论】:

      【解决方案2】:

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

      或者是因为 I/O 事件只是从根本上阻塞 操作意味着他们夺取控制权并且不将其归还 直到完成...

      即使没有框架提供自己的 IO 工作线程池,也可以实现异步 IO。事实上,只要底层(操作系统)提供某种机制来做非阻塞 IO,它就可以完全不用任何多线程来完成。

      这通常归结为像 POSIX 的 select(或 Microsoft 的 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-likes 上传递给select

      这通常是单线程、单进程服务器应用程序“处理”多个同时连接的方式。

      【讨论】:

        【解决方案3】:

        假设您在一家面包店操作收银机。您按顺序和同步地处理客户,如下所示:

        1. 接单
        2. 告诉面包师烤面包
        3. 等到面包烤好
        4. 充值
        5. 送面包
        6. 转到 1 -- 下一位客户

        那会很慢。现在,尝试按顺序接受订单,但异步处理您的客户:

        1. 接单
        2. 告诉面包师烤面包,完成后通知您。收到通知时:
          1. 充值
          2. 送面包
        3. 转到 1 -- 下一位客户

        更新:我重构了上述内容,因此它更类似于回调。您,收银员,将在向面包师下订单后立即执行第 3 步。当面包师通知您面包准备好时,您将进入第 2.1 步。

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

        现在,您可以对此进行各种幻想,预先收取费用,并告诉客户在桌子的另一端拿起面包,或类似的东西。我认为星巴克以这种方式非常“有事件”。收银员接受订单,发出一些物品请求,并告诉客户等到所有东西都放在取货区。超级高效。

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

        您和您的朋友是两个并行运行的单线程事件循环。这类似于两个接受请求的 node.js 进程。你不需要做任何复杂的事情来并行化它,你只需要再运行一个事件循环。

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

        现在,node.js 还为 IO 操作了一个内部线程池。这是从你身上抽象出来的:继续面包店的类比,假设你有一个面包师的“面包师池”——对你来说,站在收银台前,你不必知道这一点。您只需给他们订单(“一个酵母面包”)并在您收到订单完成时交付该订单。但是面包师们在他们自己的“面包师池”中同时烘烤面包。

        【讨论】:

        • 感谢您的精彩比喻。进一步澄清。在此示例中,收银员收到订单并将其交还给厨房,然后他可以去处理另一个订单。现在这里的订单接受者是主要的服务器功能..厨房是所有具有自己回调的委托功能。如果服务器函数委托给一个函数并给他们一个回调并说..完成后返回给我..如果厨房没有产生子进程的方法..在回调之前它不会阻塞发送了吗?
        • 厨房有内螺纹池。所以它会有一些线程来分配任务。但这是在 Node.js 中从您那里抽象出来的,并且这些都不会在您的事件循环中执行,因此它根本不会影响您的代码。如果您在自己的代码中执行一些 CPU 密集型计算,这将阻塞 整个事件循环(例如,假设收银员决定自己烘烤一个面包——在此期间,他可以'不接受任何新订单)。
        • 你是说对于标准函数/回调节点可以使用内部线程池来确保异步性能,但是对于昂贵的调用,我们需要像 exec 那样设置一个工作线程?如果是这样,我怎么知道“昂贵”的门槛?这似乎是一个关键点。
        • 没错。只要您对所有 IO(FS、DB、网络等)使用异步 API,您就是安全的。但是如果你需要做一些昂贵的计算,你应该使用一个子进程——甚至是一些工作池场景。我不能给你一个阈值,你需要做基准测试/分析来找出瓶颈。
        • @Linus,你的意思是异步API,它们都有自己的内部线程池,因为获得非阻塞状态的唯一方法是通过产生一个新线程,问题是我们是否明确地这样做或者框架为我们做这件事。对!!
        【解决方案4】:

        我的英语不好,所以我不明白你的确切意思。 但我可以说“多线程”和“异步”是相似但不同的术语。 即使单线程可以作为“异步”执行。

        This document 不适用于 node(它适用于 python 异步框架“twisted”)但可能对您有所帮助。

        对不起,我的英语很差。

        【讨论】:

        • 您提供的文档很棒,非常感谢!很清楚。
        • 那是一个很棒的文档。你的英语也很好:)
        • 文章很珍贵。而且你的英语很棒:)
        猜你喜欢
        • 2020-12-07
        • 2023-04-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-24
        • 2012-05-21
        相关资源
        最近更新 更多