了解事件循环

IT技术 javascript multithreading node.js asynchronous event-loop
2021-01-29 03:11:16

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

让我们看看下面的这段代码:

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

一个请求进来,JS引擎开始一步步执行上面的代码。前两个调用是同步调用。但是当涉及到setTimeout方法时,它变成了异步执行。但是 JS 立即从中返回并继续执行,即调用Non-Blockingor Async它继续在其他方面工作。

执行结果如下:

数据库

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

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

但我的问题围绕以下项目出现:

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

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

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

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

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

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

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

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

在此处输入图片说明

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

4个回答

1:如果我们说的是单线程应用,那么在JS引擎接受更多请求并执行它们的同时,是什么处理setTimeouts?那个单线程不是会继续处理其他请求吗?那么谁将继续处理 setTimeout 而其他请求不断到来并被执行。

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

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

需要理解的一个关键是节点依赖于操作系统来完成大部分繁重的工作。所以传入的网络请求实际上是由操作系统本身跟踪的,当节点准备好处理一个请求时,它只使用系统调用来向操作系统请求一个网络请求,并准备好处理数据。IO“工作”节点的大部分内容要么是“嘿,操作系统,已建立网络连接并准备好读取数据?” 或“嘿操作系统,我的任何未完成的文件系统调用都准备好了数据?”。基于其内部算法和事件循环引擎设计,node 会选择 JavaScript 的一个“tick”来执行,运行它,然后再次重复这个过程。这就是事件循环的意思。Node 基本上一直在确定“我应该运行的下一点 JavaScript 是什么?”,然后运行它。setTimeoutprocess.nextTick

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

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

3:JS引擎如何知道它是否是异步函数,以便将其放入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 的每一次滴答都必须在合理的时间内产生事件循环,否则整个架构就会失败。

通常它会到达队列的末尾,但process.nextTickvs setTimeoutvs的语义setImmediate略有不同,尽管您不必真正关心。我有一篇名为 setTimeout博客文章和更详细的朋友
2021-03-11 03:11:16
@SheshPai 当用英文段落编写代码时,每个人都无法讨论代码。只需发布一个带有代码片段的新问题,这样人们就可以根据代码而不是代码描述来回答,这会留下很多歧义。
2021-03-13 03:11:16
youtube.com/watch?v=QyUFheng6J0&spfreload=5这是对 JavaScript 引擎的另一个很好的解释
2021-03-28 03:11:16
假设我有一个队列需要服务器 1 分钟来执行,第一件事是一些异步函数在 10 秒后完成。它会进入队列的末尾,还是会在准备就绪的瞬间将自己推入队列?
2021-04-06 03:11:16
你能详细说明一下吗?假设我有两个回调,第一个有执行时间为 10 毫秒的 changeColor 方法和 1 分钟的 setTimeout,第二个有执行时间为 50 毫秒的 changeBackground 方法和 10 秒的 setTimeout。我觉得 changeBackground 首先在队列中,然后是 changeColor。之后,事件循环同步选择方法。我对吗?
2021-04-06 03:11:16

不要认为宿主进程是单线程的,他们不是。单线程是执行您的 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 秒过去了,它运行 inAWhile 函数,依此类推......

尽管 setTimeout 是在主线程中实现的。因此,您的示例中没有任何内容需要单独的线程。事实上,在浏览器中,只有选项卡是在多线程中实现的。在单个选项卡中,包括建立多个并行网络连接、等待鼠标点击、设置超时、动画等在内的所有进程都在同一线程中完成
2021-03-24 03:11:16

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

大多数开发人员都知道,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 引擎执行第一行并在控制台打印“Start”

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

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

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

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

在此处输入图片说明

嗨@nullhook,微任务队列将始终获得第一优先级,您可以查看developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/...There are two key differences........
2021-03-19 03:11:16
您是否尝试过阅读事件循环模型?
2021-04-04 03:11:16
事件循环最初不会从微队列中获取任务。事实上,它从任务队列中取出第一个任务,如果它是空的,那么它会检查微队列。无论哪种方式,任务队列中最早的任务都会首先执行。例如,如果你有一个 0 的 setTimeout 和一个 Promise ...... Promise 回调将首先执行,因为 0 的 setTimeout 意味着 4ms 的分钟,并且只有当时间过去时它才会作为任务入队。这是模型:html.spec.whatwg.org/multipage/...
2021-04-09 03:11:16

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

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

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

任务被设置 - 引擎处理它们 - 然后等待更多任务(同时休眠并消耗接近零的 CPU)。可能会发生在引擎繁忙时任务到来,然后将其排队的情况。任务形成一个队列,即所谓的“宏任务队列

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

在此处输入图片说明