-
Notifications
You must be signed in to change notification settings - Fork 193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
44.理解事件循环一(浅析) #47
Comments
great! |
好奇,文中的图片 左边的编辑器师怎么做的 |
@zhanglun 左边其实就是 sublime 截的图 |
好文 正好时间循环机制不是很了解 |
写的很详细,赞。 |
学习了事件循环 |
为什么在 Cycle2 中 setInterval 和 setTimeout 同样是 macrotask,但是先执行 setInterval 呢? |
“microtasks 队列清空” |
这个sublime主题很好看,可以搜到么 |
@Aaaaaaaty 主题是我自己改的颜色 |
@ccforward 啊这样 谢谢啦 文章好nice 持续关注中~ |
我有一个问题, Promise.then 里的 callback 是 microtask 吗? 这个不应该是 macrotask 吗? |
@mrcodehang 是microtask,可以参考这篇文章,promise都应该是microtask,在一个次循环后串行打印结果,只是有的环境将promise callback解释为macrotask而不是microtask导致打印顺序出了问题。至于为什么环境之间对它的解释不一样,这似乎涉及到ECMA和HTML之间的一些标准界定了=。=毕竟厂商们都是自己按照标准来撸一套环境 |
@ccforward 謝謝樓主分享如此詳盡的比較與分析。 |
这段代码在chrome console中执行,第二个setInterval输出了两次,是什么情况? |
请教下作者 async/await 属不属于microtasks |
@lawpachi async/await 相当于是 Promise。应该是属于 microtasks |
手动夸一夸作者,整理的很赞~ |
这个例子很方便理解!有个疑问:第9步是否属于Cycle 5? |
我想请教,分为两个任务队列的原因。 |
@justahole 并不能理解成只有两个任务队列,而是两种类型的任务队列。
|
恩,我知道,但是这样设计的目的是啥
掬一捧 <[email protected]> 于 2019年3月22日周五 下午6:19写道:
… @justahole <https://github.com/justahole> 并不能理解成只有两个任务队列,而是两种类型的任务队列。
- microtask 类型:会在一次事件循环中清空掉该队列中所有的任务。
- macrotask 类型:在一次事件循环中只会执行该队列中最前面的那个任务,剩下的任务在后续的事件循环中进行。
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#47 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AaBDq_F-DvqD0Hz5f5RhOpKu_UHpid43ks5vZK5FgaJpZM4Kosox>
.
|
Node.js 事件循环一: 浅析
多数的网站不需要大量计算,程序花费的时间主要集中在磁盘 I/O 和网络 I/O 上面
SSD读取很快,但和CPU处理指令的速度比起来也不在一个数量级上,而且网络上一个数据包来回的时间更慢:
一个数据包来回的延迟平均320ms(我网速慢,ping国内网站会更快),这段时间内一个普通 cpu 执行几千万个周期应该没问题
因此异步IO就要发挥作用了,比如用多线程,如果用 Java 去读一个文件,这是一个阻塞的操作,在等待数据返回的过程中什么也干不了,因此就开一个新的线程来处理文件读取,读取操作结束后再去通知主线程。
这样虽然行得通,但是代码写起来比较麻烦。像 Node.js V8 这种无法开一个线程的怎么办?
先看下面函数执行过程
栈 Stack
当我们调用一个函数,它的地址、参数、局部变量都会压入到一个 stack 中
函数
fire
首先被调用fire
调用sumSqrt
函数 参数为3和4之后调用
square
参数为 x, x==3当
square
执行结束返回时,从 stack 中弹出,并将返回值赋值给 s1s1加入到 sumSqrt 的 stack frame 中
以同样的方式调用下一个
square
函数在下一行的表达式中计算出 s1+s2 并赋值给 sum
之后调用
Math.sqrt
参数为sum现在就剩下
sumSqrt
函数返回计算结果了返回值赋值给 result
在 console 中打印出 result
最终
fire
没有任何返回值 从stack中弹出 stack也清空了当函数执行完毕后本地变量会从 stack 中弹出,这只有在使用 numbers string boolean 这种基本数据类型时才会发生。而对象、数组的值是存在于 heap(堆) 中的,stack 只存放了他们对应的指针。
当函数之行结束从 stack 中弹出来时,只有对象的指针被弹出,而真正的值依然存在 heap 中,然后由垃圾回收器自动的清理回收。
事件循环
通过一个例子来了解函数的执行顺序
请求
http://localhost:5000/
后打印出虽然 V8 是单线程的,但底层的 C++ API 却不是。这意味着当我们执行一些非阻塞的操作,Node会调用一些代码,与引擎里的js代码同时执行。一旦这个隐藏的线程收到了等待的返回值或者抛出一个异常,之前提供的回调函数就会执行。
上面的说的Node调用的一些代码其实就是 libuv,一个开源的跨平台的异步 I/O 。最初就是为 Node.js 开发的,现在很多项目都在用
任务队列
javascript 是单线程事件驱动的语言,那我们可以给时间添加监听器,当事件触发时,监听器就能执行回调函数。
当我们去调用
setTimeout
http.get
fs.readFile
, Node.js 会把这些定时器、http、IO操作发送给另一个线程以保证V8继续执行我们的代码。然而我们只有一个主线程和一个 call-stack ,这样当一个读取文件的操作还在执行时,有一个网络请求request过来,那这时他的回调就需要等stack变空才能执行。
回调函数正在等待轮到自己执行所排的队就被称为任务队列(或者事件队列、消息队列)。每当主线程完成前一个任务,回调函数就会在一个无限循环圈里被调用,因此这个圈被称为事件循环。
我们前面那个获取文章的例子的执行顺序就会如下:
request
事件触发getArticle
getArticle
压入(push) stackfetchArticle
被调用 同时压入 stackMath.floor
和Math.random
被调用压入 stack 然后再 弹出(pop), 从 aids 里面取出的一个值被赋值给变量 aidsuperagent.get
被执行,参数为'http://news-at.zhihu.com/api/4/news/${aid}'
,并且回调函数注册给了end
事件http://news-at.zhihu.com/api/4/news/${aid}
的HTTP请求被发送到后台线程,然后函数继续往下执行'Now is fetching an article'
打印在 console 中。 函数fetchArticle
返回print
函数被调用,'Print something'
打印在 console 中getArticle
返回,并从 stack 中弹出, stack 为空http://news-at.zhihu.com/api/4/news/${aid}
发送相应信息end
事件被触发end
事件的匿名回调函数被执行,这个匿名函数和他闭包中的所有变量压入 stack,这意味着这个匿名函数可以访问并修改express
,superagent
,app
,aids
,req
,res
,aid
的值以及之前所有已经定义的函数res.send()
伴随着 200 或 500 的状态码被执行,但同时又被放入到后台线程中,因此 响应流 不会阻塞我们函数的执行。匿名函数也被 pop 出 stack。Microtasks Macrotasks
任务队列不止一个,还有 microtasks 和 macrotasks
microtasks:
macrotasks:
这两个的详细区别下一篇再写,先看一段代码
理解了node的事件循环还是比较容易得出答案的:
根据 WHATVG 的说明,在一个事件循环的周期(cycle)中一个 (macro)task 应该从 macrotask 队列开始执行。当这个 macrotask 结束后,所有的 microtasks 将在同一个 cycle 中执行。在 microtasks 执行时还可以加入更多的 microtask,然后一个一个的执行,直到 microtask 队列清空。
规范理解起来有点晦涩,来看下上面的例子
Cycle 1
1)
setInterval
被列为 task2)
setTimeout 1
被列为 task3)
Promise.resolve 1
中两个then
被列为 microtask4) stack 清空 microtasks 执行
任务队列:
setInterval
setTimeout 1
Cycle 2
5) microtasks 队列清空
setInteval
的回调可以执行。另一个setInterval
被列为 task , 位于setTimeout 1
后面任务队列:
setTimeout 1
setInterval
Cycle 3
6) microtask 队列清空,
setTimeout 1
的回调可以执行,promise 3
和promise 4
被列为 microtasks7)
promise 3
和promise 4
执行。setTimeout 2
被列为 task任务队列
setInterval
setTimeout 2
Cycle 4
8) microtask 队列清空
setInteval
的回调可以执行。然后另一个setInterval
被列为 task ,位于setTimeout 2
后面任务队列:
setTimeout 2
setInterval
9)
setTimeout 2
的回调执行,promise 5
和promise 6
被列为 microtasks现在
promise 5
和promise 6
的回调应该执行,并且 clear 掉interval
。 但有的时候不知道为什么setInterval
还会在执行一遍,变成下面结果但是把上面的代码放入 chrome console 中执行却没有问题。这一点还要再根据不同的 node版本 查一下。
Last
这篇只是对 事件循环 的浅析和总览,后面再继续深入的研究。
The text was updated successfully, but these errors were encountered: