了解事件循环

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

让我们看看下面的代码:

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是第一个完成的,它的回调函数比第一个更早执行,这是有意义的。

我们在这里讨论的是单线程应用程序。JSEngine 一直执行这个命令,除非它完成第一个请求,否则不会转到第二个请求。但是好的一面是它不会等到像 setTimeout这样的阻塞操作解决,因此它会更快,因为它接受新的传入请求。

但我的问题出现在以下几个方面:

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

# 2: 如果这些 setTimeout函数在后台执行,同时有更多的请求进入并执行,那么是什么在后台执行异步执行呢?我们说的 EventLoop是什么?

但是不是应该把整个方法放在 EventLoop中,这样就可以执行整个方法并调用回调方法?下面是我在讨论回调函数时的理解:

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

但是在这种情况下,JS Engine 如何知道它是否是一个异步函数,以便将回调放在 EventLoop中?也许类似于 C # 中的 async关键字或某种表明 JS Engine 将采用的方法是异步方法的属性,应该相应地进行处理。

但是 文章的结果与我猜想的完全相反:

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

这里有一张图片可能会有所帮助,但是图片中的第一个解释说的和问题4中提到的一模一样:

enter image description here

所以我在这里的问题是要得到一些关于上面列出的项目的澄清?

55257 次浏览

不要认为主机进程是单线程的,它们不是。单线程是执行 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、读取文件、收到网络数据包等)

当您的代码完成时,它将从事件循环中删除,主机进程将返回来检查它,以查看是否有更多的代码要运行。事件循环还包含两个事件处理程序: 一个现在执行(just Now 函数) ,另一个在一秒内执行(inAwhile 函数)。

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

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

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

现在关于“执行 setTimeouts”,当您调用 setTimeout时,所有节点所做的基本上都是更新将在将来某个时候执行的函数的数据结构。它基本上有一大堆需要处理的事情,事件循环的每个“嘀嗒”都会选择一个,从队列中删除它,然后运行它。

需要理解的一个关键问题是,节点依赖于操作系统完成大部分繁重的工作。所以传入的网络请求实际上是由操作系统自己跟踪的,当节点准备好处理网络请求时,它只需要使用一个系统调用向操作系统请求一个网络请求,其中包含准备处理的数据。所以 IO“工作”节点所做的大部分工作是“嘿,操作系统,获得一个网络连接与数据准备读取?”或者“嘿,操作系统,我的任何未完成的文件系统调用都准备好数据了吗?”.根据其内部算法和事件循环引擎设计,节点将选择一个 JavaScript“剔”执行,运行它,然后重复整个过程。这就是事件循环的含义。Node 基本上在任何时候都决定“下一步我应该运行哪些 JavaScript 代码?”然后运行它。这个因素包括操作系统已经完成的 IO,以及通过调用 setTimeoutprocess.nextTick在 JavaScript 中排队的内容。

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

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

3: JS Engine 如何知道它是否是一个异步函数,以便将其放入 EventLoop?

节点核心中有一组固定的函数是异步的,因为它们进行系统调用,而节点知道哪些函数是异步的,因为它们必须调用 OS 或 C + + 。基本上所有的网络和文件系统 IO 以及子进程交互都是异步的,JavaScript 让节点异步运行的唯一方法就是调用节点核心库提供的一个异步函数。即使您使用的 npm 包定义了自己的 API,为了产生事件循环,最终 npm 包的代码将调用节点核心的一个异步函数,这时节点知道嘀嗒声已经完成,可以再次启动事件循环算法。

事件循环是一个回调函数队列。当一个异步函数执行时,回调函数被推送到队列中。在执行异步函数之后,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 的每一次跳动都必须在合理的时间内产生事件循环,否则整个架构就会失败。

事件循环有一个简单的任务——监视 给 Stack 打电话回调队列微任务队列。如果调用堆栈为空,事件循环将从微任务队列获取第一个事件,然后从回调队列获取第一个事件,并将其推送到调用堆栈,该事件将有效地运行该事件。这样的迭代在事件循环中称为刻度。

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

例如:

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

注意 : < em > 所有同步代码执行完毕后,异步代码将运行。

理解代码如何逐行执行

JS 引擎执行第1行并在控制台中打印“ Start”

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

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

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

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

enter image description here

JavaScript 是高级的单线程语言,直译语言。这意味着它需要一个解释器将 JS 代码转换为机器代码。解释器的意思是引擎。V8引擎的铬和网络工具包的狩猎。每个引擎都包含内存、调用堆栈、事件循环、定时器、 web API、事件等。

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

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

任务被设置-引擎处理它们-然后等待更多的任务(在睡眠和消耗接近零 CPU 的情况下)。可能发生的情况是,当引擎处于忙碌状态时,任务来了,然后它被排队了。这些任务形成一个队列,即所谓的“ 宏任务队列宏任务队列

微任务完全来自于我们的代码。它们通常是由承诺创造的: 执行。然后/catch/finally 处理程序变成一个微任务。微任务也是在“等待”的掩护下使用的,因为它是另一种形式的承诺处理。在每个宏任务之后,引擎立即执行来自微任务队列的所有任务,然后再运行任何其他宏任务或呈现或任何其他任务。

enter image description here