了解事件循环
我在想这个,这就是我想到的:
假设我们有这样的代码:
console.clear(); console.log("a"); setTimeout(function(){console.log("b");},1000); console.log("c"); setTimeout(function(){console.log("d");},0);
一个请求进来,JS引擎开始执行上面的代码一步一步。 前两个电话是同步呼叫。 但是,当涉及到setTimeout
方法时,它将成为asynchronous执行。 但JS立即从它返回并继续执行,这被称为Non-Blocking
或Async
。 并继续在其他等工作
这个执行的结果如下:
ACDB
所以基本上第二个setTimeout
首先完成,它的callback函数比第一个执行得早,这是有道理的。
我们在这里讨论单线程应用程序。 JS引擎继续执行这个,除非它完成第一个请求,它不会去第二个。 但好处是,它不会等待像setTimeout
这样的阻塞操作来parsing,所以它会更快,因为它接受新的传入请求。
但是我的问题出现在以下几个方面:
#1:如果我们正在讨论单线程应用程序,那么当JS引擎接受更多请求并执行它们时,什么机制处理setTimeouts
呢? 单线程如何继续处理其他请求? 在其他请求保持进入并执行的情况下, setTimeout
是如何工作的。
#2:如果这些setTimeout
函数在更多的请求进入和执行后在幕后执行,那么在后台执行asynchronous执行是什么? 我们所说的这个叫做EventLoop
东西是什么?
#3:但是不应该把整个方法放在EventLoop
这样整个事情就会被执行,callback方法会被调用吗? 这是我在谈论callback函数时所理解的:
function downloadFile(filePath, callback) { blah.downloadFile(filePath); callback(); }
但在这种情况下,JS引擎如何知道它是否是asynchronous函数,以便将callback放入EventLoop? Perhaps something like the
EventLoop? Perhaps something like the
C#中EventLoop? Perhaps something like the
async`关键字或某种指示JS引擎将采取的方法的属性是asynchronous方法,应该作相应的处理。
#4:但是有一篇文章与我猜测的事情可能如何工作完全相反:
事件循环是一个callback函数的队列。 当asynchronous函数执行时,callback函数被推入队列中。 JavaScript引擎不会开始处理事件循环,直到执行了asynchronous函数之后的代码。
#5:这里有这个图像可能会有所帮助,但是图像中的第一个解释是在说问题4中提到的完全相同的东西:
所以我的问题是要对上面列出的项目做一些澄清?
1:如果我们正在讨论一个单线程应用程序,那么当JS引擎接受更多请求并执行它们时,什么处理setTimeouts呢? 是不是单线程将继续工作在其他请求? 然后,谁将继续在setTimeout上工作,而其他请求继续来执行。
节点进程中只有一个线程可以执行你的程序的JavaScript。 但是,在节点本身中,实际上有几个线程处理事件循环机制的操作,这包括一个IO线程池和一些其他的线程池。 关键是这些线程的数量并不对应于像在每个连接线程的并发模型中那样处理的并发连接的数量。
现在关于“执行setTimeouts”,在调用setTimeout
,所有节点都会基本上更新将来要执行的函数的数据结构。 它基本上有一堆需要做的事情,事件循环的每个“滴答”select一个,从队列中删除它,并运行它。
要了解的一个关键是节点依赖于大部分繁重的操作系统。 所以传入的networking请求实际上是由操作系统本身来跟踪的,当节点准备好处理一个请求时,它只是使用一个系统调用来向操作系统请求一个networking请求,并准备处理数据。 IO“work”节点所做的大部分工作就是“嘿OS,用数据准备好读取networking连接?” 或“嘿OS,我的任何优秀的文件系统调用数据准备好了吗?”。 基于其内部algorithm和事件循环引擎devise,节点将selectJavaScript的一个“滴答”来执行,运行,然后再重复这个过程。 这就是事件循环的含义。 Node基本上总是决定“我应该运行什么JavaScript的下一步?”,然后运行它。 IO操作系统已完成的这些因素,以及通过调用setTimeout
或process.nextTick
在JavaScript中排队的内容。
2:如果这些setTimeout在幕后执行,而更多的请求正在进入和正在执行,幕后执行asynchronous执行的事情是我们正在谈论的EventLoop?
没有JavaScript在幕后执行。 程序中的所有JavaScript运行在前台和中间,一次一个。 后台发生的事情是操作系统处理IO,节点等待准备就绪,节点pipe理其等待执行的JavaScript队列。
3:JS Engine如何知道它是否是一个asynchronous函数,以便它可以放入EventLoop?
节点核心中有一组固定的函数,它们是asynchronous的,因为它们使得系统调用和节点知道这些是因为它们必须调用OS或C ++。 基本上所有的networking和文件系统IO以及subprocess交互都将是asynchronous的,并且JavaScript可以让节点asynchronous运行的唯一方式是调用节点核心库提供的asynchronous函数之一。 即使你正在使用一个定义了它自己的API的npm包,为了产生事件循环,最终那个npm包的代码将会调用节点核心的asynchronous函数,当节点知道tick已经完成并且它可以启动事件循环algorithm。
4事件循环是一个callback函数的队列。 当asynchronous函数执行时,callback函数被推入队列中。 JavaScript引擎不会开始处理事件循环,直到执行了asynchronous函数之后的代码。
是的,这是事实,但这是误导。 关键是正常的模式是:
//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的每一次滴答都必须在合理的时间内产生事件循环,否则整体架构将失败。
Philip Roberts有一个非常棒的video教程,它以最简单和概念化的方式解释了JavaScript事件循环。 每个JavaScript开发人员应该看看。
这里是Youtube上的video链接 。
不要认为主机进程是单线程的,他们不是。 什么是单线程是执行您的JavaScript代码的主机进程的一部分。
除了背景工作人员 ,但这些复杂的情况…
所以,你所有的js代码都运行在同一个线程中,并且不可能让你的js代码的两个不同部分同时运行(所以你没有并发nigthmare来pipe理)。
正在执行的js代码是主机进程从事件循环中拾取的最后一个代码。 在你的代码中,你基本上可以做两件事情:运行同步指令,并计划将来要执行的function。
以下是我的示例代码的心理表征(请注意:只是,我不知道浏览器实现细节!):
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,读取文件,收到networking数据包等)
当代码完成时,它将从事件循环中被移除,主机进程返回到检查它,看是否有更多的代码要运行。 事件循环包含两个事件处理函数:一个是现在执行的(justNow函数),另一个是inAWhile函数。
主机进程现在尝试匹配发生的所有事件,看是否有处理程序注册它们。 它发现justNow正在等待的事件已经发生,所以它开始运行它的代码。 当justNow函数退出时,它再次检查事件循环,search事件处理程序。 假设1秒已经过去,它运行inAWhile函数,等等….