了解事件循环

Understanding the Event Loop

提问人:Tarik 提问时间:2/6/2014 最后编辑:Ziyaddin SadigovTarik 更新时间:12/6/2022 访问量:57825

问:

我正在考虑它,这就是我想出的:

让我们看看下面的代码:

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

一个请求进来,JS引擎开始一步一步地执行上面的代码。前两个调用是同步调用。但是当涉及到方法时,它变成了异步执行。但是JS会立即从中返回并继续执行,这称为或。它继续致力于其他等。setTimeoutNon-BlockingAsync

此执行的结果如下:

一、中、德、乙

所以基本上第二个是第一个完成的,它的回调函数比第一个更早执行,这是有道理的。setTimeout

我们在这里谈论的是单线程应用程序。JS 引擎会继续执行它,除非它完成第一个请求,否则它不会进入第二个请求。但好消息是,它不会等待阻塞操作(如解析),因此它会更快,因为它接受新的传入请求。setTimeout

但我的问题围绕以下几点出现:

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

#2:如果这些函数在后台执行,而更多的请求正在传入和执行,那么在后台执行异步执行的是什么?我们谈论的这个东西叫什么?setTimeoutEventLoop

#3:但是不应该把整个方法放在里面,这样整个事情就会被执行,回调方法就会被调用吗?这是我在谈论回调函数时所理解的:EventLoop

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

但是在这种情况下,JS引擎如何知道它是否是一个异步函数,以便它可以将回调放在 ?也许像 C# 中的关键字或某种属性之类的属性,指示 JS 引擎将采用的方法是一种异步方法,应该相应地处理。EventLoopasync

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

事件循环是回调函数的队列。当异步 函数执行时,回调函数被推送到队列中。这 JavaScript 引擎不会开始处理事件循环,直到 执行异步函数后的代码。

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

enter image description here

所以我在这里的问题是要对上面列出的项目进行一些澄清?

JavaScript 多线程节点 .js 异步 事件循环

评论

1赞 Denys Séguret 2/6/2014
线程不是处理这些问题的正确比喻。思考事件。
1赞 Tarik 2/6/2014
@dystroy : 很高兴看到一个代码示例来说明JS中的事件隐喻。
0赞 Denys Séguret 2/6/2014
我不明白你在这里的问题到底是什么。
1赞 Tarik 2/6/2014
@dystroy:我在这里的问题是要对上面列出的项目进行一些澄清?
2赞 Denys Séguret 2/7/2014
Node 不是单线程的,但这对你来说并不重要(除了它设法在你的用户代码执行时完成其他事情)。一次最多只在用户代码中执行一个回调。

答:

12赞 Andrea Parodi 2/7/2014 #1

不要以为主机进程是单线程的,它们不是。单线程是主机进程中执行 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 函数退出时,它会再次检查事件循环,为事件的处理程序提供 searhcing。假设 1 秒过去了,它运行 inAWhile 函数,依此类推。

评论

0赞 slebetman 8/2/2020
不过,setTimeout 是在主线程中实现的。因此,您的示例中没有任何内容需要单独的线程。事实上,在浏览器中,只有选项卡是在多个线程中实现的。在单个选项卡中,所有进程,包括建立多个并行网络连接、等待鼠标点击、setTimeout、动画等,都在同一线程中完成
98赞 Peter Lyons 2/7/2014 #2

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

节点进程中只有 1 个线程将实际执行程序的 JavaScript。但是,在节点本身中,实际上有几个线程处理事件循环机制的操作,其中包括一个 IO 线程池和一些其他线程。关键是这些线程的数量与正在处理的并发连接数不对应,就像在每线程并发模型中那样。

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

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

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

没有 JavaScript 在幕后执行。程序中的所有 JavaScript 都运行在前面和中间,一次运行一个。幕后发生的事情是操作系统处理 IO,节点等待它准备就绪,节点管理其等待执行的 javascript 队列。

3:JS Engine 如何知道它是否是一个异步函数,以便它可以放在 EventLoop 中?

节点核心中有一组固定的函数是异步的,因为它们进行系统调用,节点知道这些是哪些,因为它们必须调用操作系统或 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赞 ilyo 8/18/2014
假设我有一个队列,服务器需要 1 分钟才能执行,第一件事是一些异步函数,在 10 秒后完成。它会走到队列的末尾,还是会在准备好的那一刻将自己推入队列?
4赞 Peter Lyons 8/19/2014
通常它会排在队列的末尾,但 vs vs 的语义略有不同,尽管您不必真正关心。我有一篇名为 setTimeout and friends 的博客文章,其中详细介绍了。process.nextTicksetTimeoutsetImmediate
0赞 SheshPai 4/5/2015
你能详细说明一下吗?假设我有两个回调,第一个回调有一个 changeColor 方法,执行时间为 10 毫秒,setTimeout 为 1 分钟,第二个有一个 changeBackground 方法,执行时间为 50 毫秒,setTimeout 为 10 秒。我觉得 changeBackground 首先在队列中,changeColor 将在下一个。之后,Event 循环同步选取方法。我说得对吗?
1赞 Peter Lyons 4/7/2015
@SheshPai,当用英语段落编写代码时,每个人都无法讨论代码,这太令人困惑了。只需发布一个带有代码片段的新问题,这样人们就可以根据代码而不是代码描述来回答,这会留下很多歧义。
0赞 Mukesh Kumar 2/7/2017
youtube.com/watch?v=QyUFheng6J0&spfreload=5 这是对 JavaScript 引擎的另一个很好的解释
4赞 Srikrushna 5/14/2021 #3

事件循环有一个简单的工作 - 监控调用堆栈回调队列和微任务队列。如果调用堆栈为空,则事件循环将从微任务队列中获取第一个事件,然后从回调队列中获取该事件,并将其推送到调用堆栈,由调用堆栈有效地运行它。这种迭代在事件循环中称为 tick。

正如大多数开发人员所知,Javascript 是单线程的,这意味着 javascript 中的两个语句不能并行执行,这是正确的。执行是逐行进行的,这意味着每个 javascript 语句都是同步的和阻塞的。但是,如果您使用 setTimeout() 函数(浏览器提供的 Web API),则有一种方法可以异步运行您的代码,它确保您的代码在指定时间(以毫秒为单位)后执行。

例:

console.log("Start");

setTimeout(function cbT(){
console.log("Set time out");
},5000);

fetch("http://developerstips.com/").then(function cbF(){
console.log("Call back from developerstips");
});

// Millions of line code
// for example it will take 10000 millisecond to execute

console.log("End");

setTimeout 将回调函数作为第一个参数,以毫秒为单位的时间作为第二个参数。 在浏览器控制台中执行上述语句后,它将打印

Start
End
Call back from developerstips
Set time out

注意异步代码在所有同步代码执行完毕后运行。

了解如何逐行执行代码

JS引擎执行第一行,在控制台中打印“开始”

在第二行中,它看到名为 cbT 的 setTimeout 函数,JS 引擎将 cbT 函数推送到 callBack 队列。

在此之后,指针将直接跳转到第 7 行,在那里它会看到 promise,JS 引擎将 cbF 函数推送到微任务队列。

然后它将执行数百万行代码,最后将打印“End”

主线程执行结束后,事件循环会先检查微任务队列,然后回调队列。在我们的例子中,它从微任务队列中获取 cbF 函数并将其推送到调用堆栈中,然后它将从回调队列中选取 cbT 功能并推送到调用堆栈中。

enter image description here

评论

0赞 nullspace 6/3/2021
事件循环最初不会从微队列中获取任务。事实上,它从任务队列中获取第一个任务,如果它为空,则对微队列执行检查。无论哪种方式,任务队列中最早的任务都会首先执行。例如,如果您的 setTimeout 为 0 且 Promise...Promise 回调将首先执行,因为 setTimeout 为 0 表示最小值为 4ms,并且只有在时间过后才会作为任务排队。模型如下:html.spec.whatwg.org/multipage/...
1赞 Srikrushna 6/4/2021
嗨,@nullhook,微任务队列将始终获得第一优先级,请您看看 developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/...There are two key differences........
0赞 nullspace 6/4/2021
您是否尝试过阅读事件循环模型?
1赞 MadJoRR 9/10/2022
您是否尝试将超时延迟设置为 0ms(用于 cbT 回调)?这难道不能证明微任务队列的优先级高于回调队列吗?当您使用较长的超时,我相信只是 setTimeout API 在这次大超时后将回调放入回调队列中,此时事件循环已经通过微任务队列。意义?也许证据就在这里(消除了有关获取实现、缓存等的细节):developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/......
1赞 Fernando Gabrieli 3/21/2023
@Srikrushna addEventListener() 是如何处理可用队列的?比如说,如果在点击本身发生时为“点击”添加事件侦听器回调,或者当你模拟点击的 dispatchEvent 时
-2赞 ashish siddhu 7/12/2021 #4

JavaScript 是高级、单线程语言、解释型语言。这意味着它需要一个解释器,将 JS 代码转换为机器代码。解释器的意思是引擎。用于 chrome 的 V8 引擎和用于 Safari 的 webkit。每个引擎都包含内存、调用堆栈、事件循环、计时器、Web API、事件等。

事件循环:微任务和宏任务

事件循环的概念非常简单。有一个无休止的循环,JavaScript 引擎等待任务,执行它们,然后休眠,等待更多任务

任务被设置好了——引擎会处理它们——然后等待更多任务(同时休眠和消耗接近零的 CPU)。可能会发生这样的情况:任务在引擎繁忙时出现,然后被排队。这些任务形成一个队列,即所谓的“宏任务队列

微任务完全来自我们的代码。它们通常由 promise 创建:.then/catch/finally 处理程序的执行成为微任务。微任务也被用于 await 的“掩护”,因为它是承诺处理的另一种形式。在每个宏任务之后,引擎会立即执行微任务队列中的所有任务,然后再运行任何其他宏任务或渲染或其他任何任务。

enter image description here