node.js - 单线程同步和异步混淆

标签 node.js asynchronous

假设makeBurger()将需要10秒

在同步程序中

function serveBurger() {
   makeBurger();
   makeBurger();
   console.log("READY") // Assume takes 5 seconds to log. 
}

这将总共花费25秒来执行。

因此,对于NodeJ,可以说我们制作了makeBurgerAsync()的异步版本,该过程也需要10秒钟。
function serveBurger() {
   makeBurgerAsync(function(count) {

   });
   makeBurgerAsync(function(count) {

   });  
   console.log("READY") // Assume takes 5 seconds to log. 
}

由于它是一个单线程。我很难想象在幕后到底发生了什么。
  • 因此,可以肯定的是,当函数运行时,两个异步函数都将进入事件循环,并且console.log("READY")将立即执行。
  • 但是在执行console.log("READY")时,两个异步函数都没有真正完成工作吗?由于单线程占用了console.log 5秒钟。
  • 在console.log完成之后。 CPU将有时间在两个异步之间切换,以便每次可以运行每个功能的一部分。

  • 因此,据此,函数不一定会导致更快的执行,由于事件循环之间的切换,异步可能会更慢吗?我想在一天结束时,所有内容都将分散在一个线程中,这与同步版本是一样的吗?

    我可能缺少一些非常重要的概念,所以请告诉我。谢谢。

    编辑
    如果异步操作像查询数据库之类的话,这是有道理的。基本上,nodejs只会说“嘿,数据库为我处理,而我还要做其他事情”。但是,我不了解的情况是nodejs自身中的自定义回调函数。

    EDIT2
    function makeBurger() {
        var count = 0;
        count++; // 1 time
        ...
        count++; // 999999 times
        return count;
    }
    
    function makeBurgerAsync(callback) {
        var count = 0;
        count++; // 1 time
        ...
        count++; // 999999 times
        callback(count);
    }
    

    最佳答案

    在node.js中,所有异步操作都在node.js Javascript单线程之外完成其任务。他们要么使用 native 代码线程(例如node.js中的磁盘I/O),要么根本不使用线程(例如事件驱动的网络或计时器)。

    您不能执行完全用node.js Javascript编写的同步操作,而是神奇地使其变为异步。异步操作是异步的,因为它调用以 native 代码实现并以实际上是异步的方式编写的某些函数。因此,为了使某些内容异步,必须专门编写它以使用本身与异步 native 代码实现异步的低级操作。

    这些带外操作然后通过事件队列与主要的node.js Javascript线程进行通信。当这些异步操作之一完成时,它将事件添加到Javascript事件队列中,然后当单个node.js线程完成当前正在执行的操作时,它将从事件队列中获取下一个事件并调用与该事件关联的回调。

    因此,您可以并行运行多个异步操作。与并行运行相同的3个操作相比,并行运行3个操作通常将具有更短的端到端运行时间。

    让我们检查一下现实世界中的异步情况,而不是您的伪代码:

    function doSomething() {
       fs.readFile(fname, function(err, data) {
           console.log("file read");
       });
       setTimeout(function() {
           console.log("timer fired");
       }, 100);
    
       http.get(someUrl, function(err, response, body) {
           console.log("http get finished");
       });
    
       console.log("READY");
    }
    
    doSomething();
    
    console.log("AFTER");
    

    这是逐步发生的事情:
  • fs.readFile()已启动。由于node.js使用线程池实现文件I/O,因此此操作将传递给node.js中的线程,它将在单独的线程中运行。
  • 无需等待fs.readFile()完成,就调用setTimeout()。这在libuv(node.js构建于其上的跨平台库)中使用了一个计时器子系统。这也是非阻塞的,因此注册了计时器,然后继续执行。
  • http.get()被调用。这将发送所需的http请求,然后立即返回以进一步执行。
  • console.log("READY")将运行。
  • 这三个异步操作将以不确定的顺序完成(先完成的操作将首先完成)。为了便于讨论,我们假设setTimeout()首先完成。完成后,node.js中的某些内部组件将在事件队列中插入一个带有计时器事件和已注册回调的事件。当node.js主JS线程完成执行任何其他JS时,它将从事件队列中获取下一个事件并调用与之关联的回调。
  • 出于此描述的目的,假设在执行计时器回调时,fs.readFile()操作完成。使用它自己的线程,它将在node.js事件队列中插入一个事件。
  • 现在setTimeout()回调完成。此时,JS解释器将检查事件队列中是否还有其他事件。 fs.readfile()事件在队列中,因此它捕获了该事件并调用与之关联的回调。该回调执行并完成。
  • 稍后,http.get()操作完成。在node.js内部,一个事件被添加到事件队列中。由于事件队列中没有其他内容,并且JS解释器当前未在执行,因此可以立即为该事件提供服务,并可以调用http.get()的回调。

  • 根据上述事件序列,您将在控制台中看到以下内容:
    READY
    AFTER
    timer fired
    file read
    http get finished
    

    请记住,这里的最后三行的顺序是不确定的(这只是基于不可预测的执行速度),因此此处的精确顺序仅是示例。如果您需要以特定的顺序执行这些命令,或者需要知道这三个命令何时完成,则必须添加其他代码以进行跟踪。

    既然您似乎在尝试通过使当前不异步的异步操作使代码运行更快,让我重复一遍。您不能执行完全用Java编写的同步操作并“使其异步”。您必须从头开始重写它,以使用根本上不同的异步较低级别的操作,或者必须将其传递给其他进程来执行,然后在完成时得到通知(使用辅助进程或外部进程或 native 代码)插件之类的东西)。

    关于node.js - 单线程同步和异步混淆,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43482775/

    相关文章:

    javascript - 使用 img 标签创建文本节点的子字符串时防止下载图像

    javascript - 在 Javascript 异步函数中设置全局变量

    c# - 如何编写异步 LINQ 查询?

    node.js - 如何在一次操作中保存 SailsJS 中的多条记录?

    node.js - 修剪 cloudformation 中的 secret 管理器 ARN

    node.js - 如何保护nodejs中的公共(public)动态文件夹

    c# - 异步并等待 For 循环

    javascript - promise 基础知识。如何 promise 异步 Node 代码?

    node.js - 如何在elasticsearch中设置计数器字段

    javascript - 如何使用 React Hooks API 管理异步函数