2017-04-18 98 views
2

假设makeBurger()将需要10秒单线程同步和异步混乱

在同步程序中,

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

这将需要总共25秒来执行。

因此,对于NodeJs可以说我们做了一个异步版本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将有时间在两个异步之间进行切换,以便每次都可以运行一些函数。

所以根据这个,函数不一定会导致更快的执行,由于事件循环之间的切换,异步可能会更慢?我想,在一天结束的时候,所有东西都会传播到一个单独的线程上,这个线程与同步版本是一样的。

我可能错过了一些非常大的概念,所以请让我知道。谢谢。

编辑 这是有道理的,如果异步操作都像查询数据库等,基本上将的NodeJS只是说“嘿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); 
} 
+0

这取决于makeBurgerAsync是否更像setTimeout,它推迟了事件循环中的执行,或者更像是等待某些事件使得只有在实际方法完成后才执行连接。在后一种情况下,console.log不会在5秒内完成,在两个调用完成后它将在那里。 –

+1

它取决于*为什么*需要10秒才能完成“异步”操作。如果阻塞事件循环的时间是10秒,那么它会阻塞10秒,但它只会在* console.log发生后阻塞*,因为需要10秒的操作将被推送到回调函数中队列。 (并且它将阻止在该10秒窗口期间进入的任何请求)如果10秒钟等待在例如文件i/o或http等,因此不阻塞事件循环,则没有明显的阻塞。 –

+0

@WiktorZychla“”更像是等待一些让实际方法完成后连续执行的东西。“你能澄清一下这句话吗?谢谢你的回应! – Zanko

回答

7

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

你不能把一个完全用node.js写成的同步操作变成异步。异步操作是异步的,因为它调用了一些在本地代码中实现的函数,并以实际上是异步的方式编写。因此,为了实现异步,必须专门编写它以使用低级别的操作,这些操作本身与异步本地代码实现异步。

这些带外操作,然后通过事件队列与主要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中的一些内部事件将使用定时器事件和注册回调在事件队列中插入一个事件。当node.js主JS线程完成其他任何JS时,它将从事件队列中获取下一个事件并调用与其关联的回调。
  6. 为了说明的目的,我们假设当该定时器回调正在执行时,fs.readFile()操作完成。使用它自己的线程,它会在node.js事件队列中插入一个事件。
  7. 现在setTimeout()回调完成。那时,JS解释器会检查事件队列中是否有其他事件。 fs.readfile()事件处于队列中,因此它抓住并调用与之相关的回调。该回调执行并结束。
  8. 一段时间后,http.get()操作结束。在node.js的内部,事件被添加到事件队列中。由于事件队列中没有其他东西,并且JS解释器当前没有执行,所以该事件可以立即得到服务,并且可以调用http.get()的回调。

每以上事件序列中,你会看到这个控制台:

READY 
AFTER 
timer fired 
file read 
http get finished 

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


由于看起来您正试图通过使异步的代码运行得更快,所以我要重复一遍。你不能采用完全用Javascript编写的同步操作并“使其异步”。您必须从头开始重新编写以使用根本不同的异步低级操作,否则您必须将其传递给其他进程以执行,然后在完成时收到通知(使用工作进程或外部进程或本机代码插件或类似的东西)。

+0

这解决了我的困惑!感谢你这样一个详细的职位。你不能采用完全用Javascript编写的同步操作并“使其异步”。但是,我可以在同步函数上设置setTimeout,使其有机会进入事件循环。但它没有什么好处。我猜测异步与其他资源进行交流并不会影响线程。 – Zanko

+0

您提到“fs.readFile()”已启动,由于node.js使用线程池实现文件I/O,因此此操作将传递给node.js中的一个线程,并将在单独的线程中运行。这让我感觉像node.js间接使用多个线程? – Zanko

+0

@Zanko - 当人们在node.js的内容中说“单线程”时,他们正在谈论JS解释器。,您的JS代码只运行在单个线程中,并且两段JavaScript之间永远不会有线程冲突。有一些使用本地代码的库函数(其中'fs.readFile()'是一个例子),它们使用线程来实现异步功能。这仍然不能使JS解释器以任何方式进行多线程。异步函数通过事件队列与JS解释器的单线程进行通信和同步。 – jfriend00