单线程同步和异步混淆

时间:2017-04-18 21:48:55

标签: 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. 
}

因为它是单线程。我不禁想象幕后真的发生了什么。

  1. 因此,当函数运行时,两个异步函数都将进入事件循环,console.log("READY")将立即执行。
  2. 但是console.log("READY")正在执行时,对于异步函数都没有真正的工作吗?由于单线程占用console.log 5秒。
  3. 完成console.log之后。 CPU将有时间在两个异步之间切换,以便每次都可以运行一些功能。
  4. 因此,根据这一点,该函数不一定导致更快的执行,由于事件循环之间的切换,异步可能会更慢?我想,在一天结束时,一切都将在一个线程上传播,这将与同步版本相同?

    我可能错过了一些非常重要的概念,所以请告诉我。感谢。

    修改 如果异步操作就像查询DB等,这是有道理的。基本上,nodejs只会说“Hey DB为我处理这个,而我会做其他事情”。但是,我不理解的情况是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);
    }
    

1 个答案:

答案 0 :(得分:8)

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

你不能完全使用node.js Javascript编写同步操作,并且神奇地使它异步。异步操作是异步的,因为它调用一些在本机代码中实现的函数,并以实际异步的方式编写。因此,为了使某些东西异步,必须专门编写它以使用本身与异步本机代码实现异步的低级操作。

这些带外操作,然后通过事件队列与主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");

这是一步一步发生的事情:

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

    READY
    AFTER
    timer fired
    file read
    http get finished
    

    请记住,这里最后三行的顺序是不确定的(它只是基于不可预测的执行速度),所以这里的精确顺序只是一个例子。如果您需要按特定顺序执行这些操作或需要知道完成所有这三个操作的时间,那么您必须添加其他代码才能跟踪它。

    因为看起来你试图通过制作当前异步的异步来让代码运行得更快,让我再说一遍。你不能采用完全用Javascript编写的同步操作,并使它成为异步的"。您必须从头开始重写它以使用根本不同的异步低级操作,或者您必须将其传递给其他某个进程才能执行,然后在完成后得到通知(使用工作进程或外部进程或本机代码插件或类似的东西)。