了解事件循环


135

我正在考虑,这是我想到的:

假设我们有这样的代码:

console.clear();
console.log("a");
setTimeout(function(){console.log("b");},1000);
console.log("c");
setTimeout(function(){console.log("d");},0);

一个请求进入,并且JS引擎逐步开始执行上述代码。前两个呼叫是同步呼叫。但是当涉及到setTimeout方法时,它将成为异步执行。但是JS立即从中返回并继续执行,这称为Non-BlockingAsync。并且它继续在其他等等上工作。

执行结果如下:

交流数据库

因此,基本上第二个setTimeout首先完成,其回调函数比第一个更早执行,这很有意义。

我们在这里谈论单线程应用程序。JS Engine会继续执行此操作,除非它完成第一个请求,否则它将不会转到第二个请求。但好处是它不会等待像setTimeout解析这样的阻塞操作,因此它会更快,因为它接受新的传入请求。

但是我的问题围绕以下几个方面提出:

#1:如果我们在谈论单线程应用程序,那么setTimeouts当JS引擎接受更多请求并执行它们时,什么机制会处理?单线程如何继续处理其他请求?setTimeout在其他请求不断进入并被执行的同时,什么起作用。

#2:如果这些setTimeout函数在更多请求传入并正在执行时在后台执行,那么在后台执行异步执行的是什么?我们所说的这个东西叫EventLoop什么?

#3:但是不应该将整个方法都放入EventLoop以便执行整个过程并调用回调方法吗?这是我在谈论回调函数时所了解的:

function downloadFile(filePath, callback)
{
  blah.downloadFile(filePath);
  callback();
}

但是在这种情况下,JS引擎如何知道它是否是一个异步函数,以便可以将回调函数放在EventLoop? Perhaps something like theC#中的async`关键字中或某种表明JS引擎将采用的方法的属性是异步方法并应予以相应对待。

#4:但是一篇文章说与我猜测事情可能如何运作完全相反:

事件循环是回调函数的队列。当执行异步函数时,回调函数将被推入队列。在执行异步函数后的代码之前,JavaScript引擎不会开始处理事件循环。

#5:这里有这张图片可能会有所帮助,但是图片中的第一个解释是说问题4中提到的完全相同的内容:

在此处输入图片说明

因此,我的问题是要对上述项目进行一些澄清?


1
线程不是处理这些问题的正确隐喻。思考事件。
DenysSéguret2014年

1
@dystroy:一个代码示例可以很好地说明JS中的事件隐喻。
塔里克2014年

我在这里看不到您的确切问题。
DenysSéguret2014年

1
@dystroy:我的问题是要对上面列出的项目进行一些澄清?
塔里克2014年

2
节点不是单线程的,但是对您来说并不重要(除了它可以在用户代码执行时设法完成其他事情之外)。您的用户代码中最多只能执行一次回调。
DenysSéguret2014年

Answers:


85

1:如果我们在谈论单线程应用程序,那么当JS引擎接受更多请求并执行它们时,哪些进程会处理setTimeouts?那不是单线程将继续处理其他请求吗?然后谁将继续处理setTimeout,而其他请求继续出现并被执行。

节点进程中只有1个线程可以实际执行程序的JavaScript。但是,在节点本身内部,实际上有多个线程处理事件循环机制的操作,其中包括IO线程池以及其他几个线程。关键是这些线程的数量与在每个连接线程的并发模型中处理的并发连接的数量不同。

现在关于“执行setTimeouts”,当您调用时setTimeout,所有节点所做的基本上就是更新将来一次执行的函数的数据结构。它基本上有一堆需要处理的事情,并且事件循环的每个“滴答声”都选择一个,将其从队列中删除并运行。

需要了解的关键一点是,在大多数繁重的工作中,节点都依赖于操作系统。因此,传入的网络请求实际上是由OS本身跟踪的,当节点准备好处理一个网络请求时,它仅使用系统调用向OS询问网络请求,其中包含准备好处理的数据。IO的“工作”节点所做的大部分工作就是“嘿,操作系统,已准备好读取数据的网络连接?” 或“嘿,操作系统,我所有未完成的文件系统调用都已准备好数据?”。根据其内部算法和事件循环引擎设计,节点将选择一个“ tick” JavaScript来执行,运行它,然后再次重复该过程。这就是事件循环的含义。基本上,节点始终在确定“接下来我应该运行什么JavaScript?”,然后运行它。setTimeoutprocess.nextTick

2:如果这些setTimeout将在更多请求传入和执行期间在后台执行,那么在后台执行异步执行的事情就是我们在谈论EventLoop吗?

没有JavaScript在后台执行。程序中的所有JavaScript一次都运行在前面和中间。幕后发生的事情是OS处理IO,而节点等待IO准备就绪,然后节点管理等待执行的javascript队列。

3:JS引擎如何知道它是否是异步函数,以便可以将其放入EventLoop中?

节点核心中有一组固定的函数是异步的,因为它们进行系统调用,而节点知道这些是因为必须调用OS或C ++。基本上,所有网络和文件系统IO以及子进程的交互都是异步的,JavaScript可以使节点异步运行某些东西的唯一方法是调用节点核心库提供的异步功能之一。即使您使用定义其自己的API的npm包,也要生成事件循环,最终,该npm包的代码将调用节点核心的异步函数之一,这是在节点知道滴答声已完成并且可以启动事件时再次循环算法。

4事件循环是回调函数的队列。当执行异步函数时,回调函数将被推入队列。在执行异步函数后的代码之前,JavaScript引擎不会开始处理事件循环。

是的,这是对的,但这是一种误导。关键是正常模式是:

//Let's say this code is running in tick 1
fs.readFile("/home/barney/colors.txt", function (error, data) {
  //The code inside this callback function will absolutely NOT run in tick 1
  //It will run in some tick >= 2
});
//This code will absolutely also run in tick 1
//HOWEVER, typically there's not much else to do here,
//so at some point soon after queueing up some async IO, this tick
//will have nothing useful to do so it will just end because the IO result
//is necessary before anything useful can be done

因此,是的,您可以完全同步地计数所有内存中的斐波那契数,从而完全阻塞事件循环,是的,这将完全冻结您的程序。这是合作并发。JavaScript的每一个滴答声都必须在合理的时间内产生事件循环,否则整个架构就会失败。


1
可以说我有一个队列,它将花费服务器1分钟来执行,并且第一件事是一些异步功能在10秒后完成。它会到达队列末尾还是在准备就绪后立即将自己推入队列?
ilyo 2014年

4
一般来说,将进入队列的末尾,但语义process.nextTickVS setTimeoutVS setImmediate有细微的差别,但你真的不应该去关心。我有一篇名为setTimeout和朋友博客文章,其中有更详细的介绍。
彼得·里昂斯

你能详细说明一下吗?让我们说我有两个回调,第一个有一个changeColor方法,执行时间为10毫秒,setTimeout为1分钟,第二个有一个changeBackground方法,执行时间为50毫秒,setTimeout为10秒。我觉得changeBackground首先在队列中,而changeColor将在下一个。之后,事件循环将同步选择方法。我对吗?
SheshPai 2015年

1
@SheshPai用英语段落编写代码时,每个人都无法讨论代码。只需用代码片段发布一个新问题,以便人们可以根据代码而不是对代码的描述进行回答,这会造成很多歧义。
彼得·里昂斯

youtube.com/watch?v=QyUFheng6J0&spfreload=5这是JavaScript引擎的另一个很好的解释
Mukesh Kumar

65

Philip Roberts提供了一个很棒的视频教程,它以最简单和概念性的方式解释了javascript事件循环。每个javascript开发人员都应该看看。

这是Youtube上的视频链接


16
我观看了,这确实是有史以来最好的解释。
塔里克

1
必须观看JavaScript爱好者和发烧友的视频。
Nirus

1
该视频改变了我的生活^^
HuyTran '17

1
来到这里游荡..这是我得到的最好的解释之一..谢谢您的分享...:D
RohitS

1
这是一个eyeopener
HebleV

11

不要认为主机进程是单线程的,不是。单线程是宿主进程中执行您的JavaScript代码的部分。

除了后台工作人员外,但这会使情况变得复杂。

因此,所有js代码都在同一线程中运行,并且不可能让js代码的两个不同部分同时运行(因此,您无法管理并发性)。

正在执行的js代码是主机进程从事件循环中获取的最后一个代码。在您的代码中,您基本上可以做两件事:运行同步指令,以及安排某些事件发生时将来执行的功能。

这是我对示例代码的看法(请注意:仅此而已,我不知道浏览器的实现细节!):

console.clear();                                   //exec sync
console.log("a");                                  //exec sync
setTimeout(                //schedule inAWhile to be executed at now +1 s 
    function inAWhile(){
        console.log("b");
    },1000);    
console.log("c");                                  //exec sync
setTimeout(
    function justNow(){          //schedule justNow to be executed just now
        console.log("d");
},0);       

当您的代码运行时,宿主进程中的另一个线程会跟踪发生的所有系统事件(单击UI,读取文件,接收到网络数据包等)。

代码完成后,将其从事件循环中删除,主机进程返回对其进行检查,以查看是否还有更多代码要运行。事件循环包含两个以上的事件处理程序:一个立即执行(justNow函数),另一个在第二秒内执行(inAWhile函数)。

现在,主机进程尝试匹配发生的所有事件,以查看是否为它们注册了处理程序。它发现justNow正在等待的事件已经发生,因此它开始运行其代码。当justNow函数退出时,它将再次检查事件循环,搜索事件处理程序。假设经过了1 s,它将运行inAWhile函数,依此类推。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.