Event Loop 是什么?
Event Loop 是什么?
本文写于 2020 年 12 月 6 日
广义上来说 Event Loop 并不是 JavaScript 独有的概念,他是一个计算机的通用概念。
狭义上来说,只有 Node.js 才有 Event Loop,浏览器并没有。
一个场景引发的困惑
为什么需要 Event Loop 呢?先看一个常见的场景,如果我们同时执行了三种不同的异步事件:
setTimeout(foo, 100);
fs.readFile('./README.md', bar);
server.on('close', doSth);
我们知道在计算机中,操作系统会帮我们新建一个「进程」使应用程序可以执行,但是一个进程在一个时刻只能执行一个任务,如果我们需要同时执行这三个任务,有三个方法:
- 新建一个进程;
- 新建一个线程;
- 排队执行。
如果不太了解,可以看我之前的一篇文章,「进程与线程」。
JavaScript 作为一门单线程的语言,肯定不能分出三条线程去执行这三条语句。那么假设计时器结束的一瞬间,三个回调函数同时触发了,Node 会怎么处理呢?
PS:Node.js 读取文件用的是 libuv,自己并不会去读写文件,所以单线程也可以异步的读写文件
我们可以推测出以下两点:
- Node.js 肯定会以某种顺序执行;
- 这种顺序应该是规定好的(优先级)。
Event Loop 的解释
回到 Event Loop 本身,我们拆开来看看。
什么叫做 Event?Event 就是事件,比如回调函数的触发就是一个事件。
什么叫做 Loop?Loop 就是循环,比如 for 循环 while 循环。
事件是有优先级的,所以处理时候是分先后的。Node.js 按照顺序去“询问”每个 Event,并且循环往复的去“询问”:
Event1 => Event2 => Event3 => Event1 => Event2 => ...
这就变成了 「poll(轮询)」。
操作系统触发事件,JavaScript 处理事件,Event Loop 就是事件处理顺序管理的「解决方案」。
维基是这么解释的:Event Loop 是一个程序结构,用于等待和发送消息和事件。
简单说,就是在程序中设置两个线程:
- 一个负责程序本身的运行,称为"主线程";
- 另一个负责主线程与其他进程的通信,被称为 「Event Loop 线程」,也有人叫他「消息线程」。
Node 中的 Event Loop
|-----------------------|
---->| timer |
| |-----------------------|
| |
| |
| |-----------------------|
| | close callbacks |
| |-----------------------|
| |
| |
| |-----------------------|
| | idle, prepare |
| |-----------------------|
| |
| | |------------------|
| |-----------------------| | incoming: |
| | poll |<----| connections, |
| |-----------------------| | data, etc. |
| | |------------------|
| |
| |-----------------------|
| | check |
| |-----------------------|
| |
| |
| |-----------------------|
-----| close callbacks |
|-----------------------|
这是 Node.js 官方文档上的示意图,我们来分析一下。
- 首先第一步,Node 会先去寻找是否存在计时器;
- 然后在看有没有其他的 I/O 相关的回调函数;
- idle 和 prepare 阶段根据字面意思推断,应该是清理一下,休息一下;
- 下一步进入轮询的阶段,检查系统事件;
- check 阶段检查 setImmediate 回调;
- close callbacks 就是处理类似 socket 关闭的事件。
这里可以看到 check 阶段检查的是 setImmediate 回调,有的面试题目可能会问 setImmediate(fn)
和 setTimeout(fn, 0)
谁先执行,这里我们就能回答了:不确定,因为 Event Loop 是个圈。
另外,setImmediate 不是一个标准特性,MDN 不建议我们在生产环境中使用它,浏览器只有新版的 IE 支持。
在 Node 的 Event Loop 中,最常用的就是:
- timer 检查;
- poll 轮询;
- check 检查。
我们先当作其他阶段都不存在了,只把这三个阶段作为循环。
因为 timer 阶段处理定时器函数回调,check 阶段处理 setImmediate 回调,所以如果这两个执行栈没有任务的话,我们就不会走这两个阶段了。
Node.js 会很智能的在其他阶段空闲时只停留在该阶段。
并且大部分时间,Node.js 都停留在 poll 阶段,文件请求、网络请求的事件处理也多半在这个阶段进行。
官方例子:
const fs = require('fs');
// 假设花费 95ms 读取文件
function someAsyncOperation() {
fs.readFile('/path/to/file', callback);
}
const timeoutScheduled = Date.now();
setTimeout(() => {
const delay = Date.now() - timeoutScheduled;
console.log(`${delay}ms 后执行了 setTimeout 的回调函数`);
}, 100);
// 95ms 的异步操作
someAsyncOperation(() => {
const startCallback = Date.now();
// 10ms 的同步操作
while (Date.now() - startCallback < 10) {
// nothing to do
}
});
这里我们花 95ms 读完文件后又做了 10ms 的同步操作。但问题是在 95ms 结束后 5ms 就需要执行定时器了呀,这里硬生生被 while
的同步操作卡住了 5ms,Node.js 在这个过程中是如何处理的呢?
- 当我们的 Event Loop 进入 poll 阶段,发现 poll 的队列是空的,因为文件没有读完。
- 于是 Event Loop 检查了一下最近的计时器,发现还需要 100ms,于是决定这段时间就停留在 poll 阶段。
- 在 poll 阶段停留了 95ms 之后,
readFile
操作完成,耗时 10ms 的同步操作被系统放入 poll 队列,执行完毕之后 poll 队列为空。 - Event Loop 紧接着又去看了一眼计时器,发现已经超时了 5ms 了。于是由经 check 阶段,close callbacks 阶段,Event Loop 又回到了 timer 阶段执行了超时计时器的回调函数。
这里如果我们一直占着 poll 阶段做同步任务会怎么样呢?
Node.js 做了限制,会有最长占用时长的,根据操作系统而定。
Event Loop 例题
我们来做几道例题吧:
例题一
fs.readFile('xx.txt', () => {
console.log('fs');
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
});
他们的输出顺序该是如何?
因为 readFile 是读取文件,该操作既不是 timer,也不是 setImmediate,所以在 poll 阶段进行。
poll 之后是 check,check 之后才是 timer。所以先执行 setImmediate,后执行 setTimeout。
例题二
setImmediate(() => {
console.log('setImmediate');
setTimeout(() => {
console.log('setTimeout in setImmediate');
}, 0);
});
setTimeout(() => {
console.log('setTimeout');
setImmediate(() => {
console.log('setImmediate in setTimeout');
});
}, 0);
这题应该分情况讨论。
情况一:
如果是 timer 先执行,那我们必然会先执行外层的 setTimeout 的回调函数。输出完 'setTimeout'
之后,将 setImmediate 的回调放入 check 阶段——注意,这里 check 的执行栈中已经有了第一个 setImmediate 了。
执行结束后,进入 check 阶段,执行第一个 setImmediate,输出 'setImmediate'
,然后执行第二个 setImmediate,输出 'setImmediate in setTimeout'
。再将 setTimeout 放入 timer 队列。执行结束。
进入 timer 队列,输出 'setTimeout in setImmediate'
。
情况二:略
例题三
console.log(1);
setTimeout(() => {
console.log('setTimeout1');
Promise.resolve('promise').then((res) => {
console.log(res);
});
}, 0);
setTimeout(() => {
console.log('setTimeout2');
}, 0);
这里我们会发现一个问题,刚刚 Node.js 的官方图没有给出任何关于 Promise 的细节,甚至于在原文中搜索 promise 也是一无所获。
那是因为 Node.js 的 Promise 是以 nextTick 为基础实现的。
nextTick 触发的时机是在当前任务队列结束之后立即执行。这里应该先输出 'setTimeout1'
,再输出 'promise'
,最后输出 'setTimeout2'
。
浏览器的 Event Loop
浏览器只有宏队列和微队列,比 Node.js 简单很多。(注意,这都不是 JS 引擎提供的,而是 Node 和浏览器提供的)。
- 宏列队:用来保存待执行的 macrotask (宏任务),比如:timer / DOM 事件监听 / ajax 回调;
- 微列队:用来保存待执行的 microtask(微任务),比如:Promise 的回调(then 方法) / MutationObserver 的回调。
在 Promise/A+的规范中,Promise 的实现可以是微任务,也可以是宏任务。但是普遍的共识表示(至少 Chrome 是这么做的),Promise 应该是属于微任务的。
当前执行栈执行完毕时,会立刻先处理所有「微任务」队列中的事件,然后再去「宏任务」队列中取出一个事件。也就是说同一次 Event Loop 中,微任务永远在宏任务之前执行。
说到这里其实很明白了,Node.js 中的 Event Loop 有很多阶段,不停的循环;而浏览器中只有两个阶段,分别查看宏任务和微任务队列。
看个例子:
setTimeout(() => console.log(1), 0);
new Promise((resolve) => {
console.log(2);
resolve('promise');
}).then((res) => {
console.log(res);
});
console.log(3);
- 当我们执行 setTimeout 的时候,将回调函数存入宏队列;
- 执行
new Promise
时,执行函数中的代码,将 then 任务存入微队列; - 执行
console.log(3)
,当前执行栈执行完毕; - 执行微队列;
- 执行宏队列。
做道题总结一下
OK,我们再看一道题目:
const foo = async () => {
console.log('foo');
await bar();
console.log('bar end');
};
const bar = async () => {
console.log('bar');
};
foo();
new Promise((resolve) => {
console.log('promise');
resolve('promise down');
}).then((res) => {
console.log(res);
});
首先第一步:因为 async
await
是 Promise 的语法糖,所以需要将思维转化成 Promise,不可以真的把这段代码当成同步代码。
- 所以先输出
'foo'
是没问题的,这相当于new Promise(() => { console.log('foo') })
一样; - 然后输出
'bar'
也是没问题的,相当于在第一次new
的Promise
中又new
了一次; - 接着输出
'bar end'
的语句应该被丢进微队列了; - 执行下方的
Promise
,输出'promise'
,将then
丢进微队列; - 执行微队列:先输出
'bar end'
,后输出'promise down'
。
(完)
Event Loop 是什么?的更多相关文章
- Atitit 解决Unhandled event loop exception错误的办法
Atitit 解决Unhandled event loop exception错误的办法 查看workspace/.metadata/.log org.eclipse.swt.SWTError: No ...
- javascript运行模式:并发模型 与Event Loop
看了阮一峰老师的JavaScript 运行机制详解:再谈Event Loop和[朴灵评注]的文章,查阅网上相关资料,把自己对javascript运行模式和EVENT loop的理解整理下,不一定对,日 ...
- [译]Node.js - Event Loop
介绍 在读这篇博客之前,我强列建议先阅读我的前两篇文章: Getting Started With Node.js Node.js - Modules 在这篇文章中,我们将学习 Node.js 中的事 ...
- Eclipse经常报Unhandled event loop exception的原因
在公司的电脑上,Eclipse经常报Unhandled event loop exception 错误,非常频繁,通过搜索发现是因为电脑上安装了百度杀毒导致的.... 无语 另外 teamviewer ...
- Node.js 事件循环(Event Loop)介绍
Node.js 事件循环(Event Loop)介绍 JavaScript是一种单线程运行但又绝不会阻塞的语言,其实现非阻塞的关键是“事件循环”和“回调机制”.Node.js在JavaScript的基 ...
- JavaScript 运行机制详解:再谈Event Loop
原文地址:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Eve ...
- PYTHON ASYNCIO: FUTURE, TASK AND THE EVENT LOOP
from :http://masnun.com/2015/11/20/python-asyncio-future-task-and-the-event-loop.html Event Loop On ...
- 数据密集型 和 cpu密集型 event loop
Node.js在官网上是这样定义的:“一个搭建在Chrome JavaScript运行时上的平台,用于构建高速.可伸缩的网络程序.Node.js采用的事件驱动.非阻塞I/O模型使它既轻量又高效,是构建 ...
- 【Node.js】Event Loop执行顺序详解
本文基于node 0.10.22版本 关于EventLoop是什么,请看阮老师写的什么是EventLoop 本文讲述的是EventLoop中的执行顺序(着重讲setImmediate, setTime ...
- [Javascript] Task queue & Event loop.
Javascript with Chorme v8 engine works like this : For Chorme engine, v8, it has call stack. And all ...
随机推荐
- 学习MySql(一)
一.安装部署mysql 1.安装mysql: # yum -y install autoconf libaio libaio-devel # groupadd mysql # useradd -r - ...
- BMZCTF simple_pop
simple_pop 打开题目得到源码 这边是php伪协议的考点,需要去读取useless.php 解码获得源码 <?php class Modifier { protected $var; p ...
- 顺利通过EMC实验(12)
- 谷歌地图 API 开发之信息窗口
信息窗口 简介 InfoWindow 在地图上方给定位置的弹出窗口中显示内容(通常为文本或图像).信息窗口具有一个内容区域和一个锥形柄.柄顶部与地图上的某指定位置相连. 通常,您会将信息窗口附加到标记 ...
- Javascript--function的name属性
1.非标准的name属性 function sayHi(){ console.log("Hi");} console.log(sayHi.name);
- Spark项目应用-电子商务大数据分析总结
一. 数据采集(要求至少爬取三千条记录,时间跨度超过一星期)数据采集到本地文件内容 爬取详见:python爬取京东评论 爬取了将近20000条数据,156个商品种类,用时2个多小时,期间中断数 ...
- 【Android开发】通过 style 设置状态栏,导航栏等的颜色
<style name="test"> <!--状态栏颜色--> <item name="colorPrimaryDark"> ...
- 小程序申请测试appid
话不多说,直接上图: 1. 登录微信官方文档: https://developers.weixin.qq.com/miniprogram/dev/devtools/sandbox.html 2. ...
- 安装scrapy速度慢解决方案
使用终端pip安装scrapy龟速 解决方案: 使用清华源下载 清华园链接 https://mirrors.tuna.tsinghua.edu.cn/help/pypi/ win+R打开cmd 输入p ...
- FastAPI(七十二)实战开发《在线课程学习系统》接口开发-- 留言列表开发
之前我们分享了FastAPI(七十一)实战开发<在线课程学习系统>接口开发-- 查看留言,这次我们分享留言列表开发. 列表获取,也需要登录,根据登录用户来获取对应的留言.逻辑梳理如下. 1 ...