js事件循环机制辨析

 对于新接触js语言的人来说,最令人困惑的大概就是事件循环机制了。最开始这也困惑了我好久,花了我几个月时间通过书本,打代码,查阅资料不停地渐进地理解他。接下来我想要和大家分享一下,虽然可能有些许错误的地方,希望大家不吝赐教,感谢感谢。

 这是所涉及的知识点:

  • 观察者模式
  • js的事件循环机制
    • js事件循环机制优缺点及与多线程的比较

观察者模式

 js的事件循环机制是基于观察者模式的,而跟观察者模式相对应的是轮询,我们先来说说轮询的原理。

 我们将轮询映射在现实世界中即为:B不停到A的房间观察房间里是否有人,从而知道A是否回来。

 但显然,这是效率极低的,我们回到代码层面上。B线程使用while(true){观察A的房间,当A在房间内时退出循环}来做到轮询。但是,这样B线程就被堵塞住了,除非退出该循环,否则无法执行接下来的同步代码及异步代码。这对于单线程语言是完全无法接受的,所以我们来看看观察者模式,他是否会堵塞线程。

 同样的,我们来将观察者模式映射到现实世界中:B在自己房间做自己的事情,不再不停地到A的房间看他是否回来,而是当A回到自己房间时,打电话通知B他回来了,B再去房间找A玩。

 该模式最大的优势就是:B可以在等待A回房间的期间,做自己的事情。回到代码层面上,使用观察者模式后,B线程不再被堵塞,A回到房间的信息不再需要B通过循环来同步地监听,而是A用消息传给B线程,B再根据这个消息来执行当A回到房间后应该执行的操作。

 其实当理解了观察者模式的大体流程就已经能够理解js的事件循环机制了。但了解得深入些也没有坏处。接下来我们来用js代码来模拟出一个简易的观察者模式。
代码如下:

var b = {
process_a:mes=>{
	console.log('刚刚A发了 %s 的信息,所以我知道A回来了,我该去他房间找他玩了。',mes)
}
}

function A(b){
	var mes_a = '我是A,我回来了'
	b.process_a(mes_a)
}

A(b)

 结果如下:

如果大家对同步,异步,堵塞,非堵塞的概念有不理解的地方的话,可以看我的 同步,异步,堵塞,非堵塞,并发 辨析。


事件循环机制

 事件循环机制的核心就是观察者模式。我先给大家描述一遍程序执行的流程。

  1. js程序进入线程,函数入栈,当遇到同步代码的时候就顺序执行,遇到异步代码时,把异步任务抛给WebAPIs执行,然后继续执行接下来的同步代码,直到栈为空。(如若大家对函数栈不了解的话可以看下我的 栈,堆辨析及使用)
  2. 在步骤1进行的同时,WebAPIs执行异步任务,当执行完一个异步任务就将其对应的回调函数放入任务队列(Callback Queue)中等待。
    • WebAPIs是由C++实现的浏览器创建的线程,处理诸如DOM事件,http请求,定时器等异步任务。
  3. 当执行栈为空时,从Callback Queue中取出队列头放入执行栈中,回到第一步。

 给大家一个我画的图,方便理解。

 不过大家可能会疑惑,事件循环机制跟观察者模式哪有什么关系?其实是这样的,在第2步中我写道

当执行完一个异步任务就将其对应的回调函数放入任务队列(Callback Queue)中。

 但我们是如何判断这个异步任务执行完了呢——观察者模式。任务队列是观察者,WebAPIs是被观察者,观察者要求被观察者当发生执行完异步任务这一事件时,通知他执行完了,并将该事件对应的回调函数传过来。

js事件循环机制优缺点及与多线程的比较

 通过事件循环机制,我们就可以实现代码的异步,从而不会堵塞线程。

 通过这一特性,

  1. js在IO上有着卓越的表现,因为IO操作不再会堵塞住线程。
  2. 可以做到高并发。稍微解释一下为什么能够高并发——当同时有多个任务要执行,js将他一一排列起来,然后按顺序执行,这样cpu就不会因为同时要处理的工作太多而负载过大。

 朴灵在《深入浅出nodeJS》中说道:

石器时代:同步。青铜时代:复制线程。白银时代:多线程。黄金时代:事件驱动。

 不过我不敢说事件驱动就是比多线程好,但他确实没有多线程的这些恼人的缺陷。

  1. 如果有大量的线程,会影响性能,因为操作系统需要在线程之间不停进行上下文切换。
  2. 通常数据是多个线程共享的,需要上锁,同时又要防止出现死锁现象。
  3. IO会堵塞住一个线程。

 但同时的,js也有他的缺陷。

  1. 不适合cpu密集型。也解释一下——如一段代码需要非常大量的计算量,以至于他长时间地占着线程,这就堵塞了,后继的同步代码及异步代码都无法执行。不过,html5推出了web worker,可以有效地解决这一缺陷,在本章不表,后面我会专门写一篇文章来讲他。
  2. 只能使用一个线程,无法充分利用计算机的多核cpu。
  3. 可靠性低,一旦一个环节崩溃则整个程序全部崩溃。

 没有一项技术是绝对完美的,但我们要清楚他的优缺点及原因,从而能够充分利用其优点,同时规避其缺点甚至通过自己的方式解决其缺点。


参考资料

  1. Advantages and Disadvantages of a Multithreaded/Multicontexted Application: https://docs.oracle.com/cd/E13203_01/tuxedo/tux71/html/pgthr5.htm
  2. https://www.hostreview.com/blog/160311-the-pros-and-cons-of-using-nodejs: https://www.hostreview.com/blog/160311-the-pros-and-cons-of-using-nodejs
  3. 理解事件循环与任务队列:https://www.jianshu.com/p/e865c3a7ba10
posted @ 2019-02-11 18:52  前端蔡蔡  阅读(1293)  评论(0编辑  收藏  举报