单线程同步和异步混淆

Zan*_*nko 3 asynchronous node.js

假设makeBurger()需要10秒钟

在同步程序中,

function serveBurger() {
   makeBurger();
   makeBurger();
   console.log("READY") // Assume takes 5 seconds to log. 
}
Run Code Online (Sandbox Code Playgroud)

执行总共需要25秒.

因此,对于NodeJ,我们假设我们制作的异步版本makeBurgerAsync()也需要10秒钟.

function serveBurger() {
   makeBurgerAsync(function(count) {

   });
   makeBurgerAsync(function(count) {

   });  
   console.log("READY") // Assume takes 5 seconds to log. 
}
Run Code Online (Sandbox Code Playgroud)

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

  1. 因此,当函数运行时,两个异步函数都将进入事件循环并立即console.log("READY")执行.
  2. 但是在console.log("READY")执行时,对于异步函数都没有真正的工作吗?由于单线程占用console.log 5秒.
  3. 在console.log完成之后.CPU将有时间在两个异步之间切换,以便每次都可以运行一些功能.

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

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

编辑 如果异步操作就像查询数据库等那么有意义.基本上,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);
}
Run Code Online (Sandbox Code Playgroud)

jfr*_*d00 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");
Run Code Online (Sandbox Code Playgroud)

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

  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()can 的回调.

根据上面的事件序列,您将在控制台中看到:

READY
AFTER
timer fired
file read
http get finished
Run Code Online (Sandbox Code Playgroud)

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


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