zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    对象池使用时要注意几点
    Flash3D学习计划(一)——3D渲染的一般管线流程
    714. Best Time to Buy and Sell Stock with Transaction Fee
    712. Minimum ASCII Delete Sum for Two Strings
    647. Palindromic Substrings(马拉车算法)
    413. Arithmetic Slices
    877. Stone Game
    338. Counting Bits
    303. Range Sum Query
    198. House Robber
  • 原文地址:https://www.cnblogs.com/caiyy/p/10362247.html
Copyright © 2011-2022 走看看