JS的Event Loop
异步编程
看了这篇博客,对异步编程有了一些了解。
大部分人编程都是从 c、python、scheme 这类典型的“同步”型语言开始的。对于同步型语言来说,他们要如何实现多任务呢?最简单的方式当然是顺序式逐个执行,这种叫“单线程同步模型”。但是这种模型在强调“响应速度”的场景下,如:用户交互式程序,就很难让人接受。另一个选择就是使用多进程或线程的方式来处理多任务,这种叫“多线程模型”或“多进程模型”。但是这种方式涉及到进程、线程间通信的问题,比较复杂,另外当进程或线程数多到一定程度的时候,CPU的资源很大一部分会被耗在切换进程或线程上。
另一种方案就是“异步模型”,就是程序员自己将多个任务切分成小片,交替进行,只要交替的速度够快,那么每个任务都能得到及时的得到响应了,用户会感觉各个任务是“同时”进行的。有没有觉得这句话很熟悉?是不是感觉有点像CPU在多进程间切换模拟多任务一样?区别在于,一个是由程序员控制,而一个是由操作系统控制。
JavaScript的异步编程
JS 实现“异步模式”的方式就是这样将一个任务拆分成多个小分片。举个简单的例子,一个页面加载的时候需要发送两个请求去请求一些数据,假设这两个任务没有相互依赖,那么用 JS 的编程模式,是如何将它们分成小分片的呢?
我们先来描述一下这两个任务要做的事情:
- 做一些准备事项
- 发送ajax请求
- 获取数据,在做一些事情
我们可以更简单的描述为:
- before ajax
- send ajax
- after response
两个任务都可以简单的描述成上方三个阶段,设任务分别为 A、B,那么典型的 JS 执行顺序可以是:
- A :: before ajax
- A :: send ajax (i/o操作)
- B :: before ajax
- B :: send ajax (i/o操作)
- B :: after response
- A :: after response
这两个任务就这样被分成6个小分片。然而,实际是并不是6个小分片,而是4个。因为2跟4都是 i/o 操作,相对其它四个而言它们其实是非常非常耗时的。然而,JS并不关心,因为 i/o 操作 JS 都转嫁给其它线程(也许是进程)来帮忙完成了。
为什么说1、3、5、6是“小分片”?因为它们被执行完毕所需要的时间其实是非常短的,只要你不作死把它们给阻塞住(死循环、调用一些阻塞 api),对我们而言,这两个任务好像真的是并行的进行。
JavaScript的Event Loop
JS 的 Event Loop 机制主要是有一个类似消息队列的队列在辅助完成的。每当遇到“异步任务”时,如:ajax 请求、定时任务,JS 将这些任务交给负责的线程去执行这些耗时的操作,自己则继续往下执行,直到当前执行栈空了,也就是当前这个小分片执行完毕了。然后,从队列中取出一个“任务的回调函数”,也就是另一个小分片压入栈中继续执行。那么队列中的“任务回调函数”怎么来的呢?程序员在定义“异步任务”的时候注册好的,当其它线程把相对应的任务执行完毕之后,就会把对应的回调函数推入队列当中。 JS 的 Event Loop 就是这么一回事了!
刚入门的人经常会问:为什么 setTimeout 时间为 0 的时候它的回调函数不是立即执行?因为定时结束后(虽然几乎是立即结束的)这个回调会先被推入队列中。如果当前调用栈被阻塞了,比如调用了 window.alert
,用户又迟迟没点掉 alert 窗口,那么这个"立即"就会被拖得很久很久。
Micro Task与Macro Task
有些时候,我们需要让一段代码在下一个 Event Loop 任务来临之前“立即执行”~
于是浏览器(不同平台实现方式不一定相同)提供了一个新的任务队列叫 “Mirco Task Queue”(微任务队列),之前的任务队列就叫做 “Macro Task Queue(宏任务队列)”。
当执行栈空的时候——也就是下一个 Event Loop 任务来临之前,JS 引擎会去检查微任务队列是不是为空,如果不为空,则把里面的任务都执行了。直到它空了之后,才会去宏任务队列取下一个任务压入栈中执行。
目前,nodejs 的 process.nextTick、promise 里的 onResolve 与 onReject 回调都是放在微任务队列的。process.nextTick 我能理解它为什么存在,我不能理解的是为什么 promise 的 onResolve 与 onReject 也要这样设计?
最后
我觉得 Mozilla 的文章质量变得很高的感觉,一篇下来简明扼要,Concurrency model and Event Loop。
异步编程、Event Loop 都不是 JS 专属,所以我学习 JS 的 Event Loop 的时候,很想知道异步编程的本质是什么?除了异步编程还有哪些编程模式?翻来翻去,我找到了这篇博客,Introduction to Asynchronous Programming,但感觉还需要进一步的了解,比如可以去体验一下多线程编程,去更深入的了解“异步模型”底层的实现(文中说到的 libevent 库)。