阅读 302

eventLoop详解

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

eventLoop,即事件循环,目的主要是为了协调事件、用户交互任务、脚本任务、渲染任务、网络任务等。js 本身是一个单线程的脚本语言,在运行上存在同步和异步两种机制,它不像 java 可以开启多个线程进行多线程操作,就 js 本身而言,它并没有 java 程序语言那么的强大,仅仅是一个脚本语言而已;所以一旦存在多线程的话,势必要考虑线程之间的通信等等问题,这样就变得复杂了;这也是之所以最开始研发人员规定 js 是一个单线程脚本语言的原因之一。js 的异步机制是由 eventLoop 和 task queue(任务队列)构成;主线程拥有一个执行栈和一个队列,主线程在执行当前执行栈的代码的时候,遇到函数会先将函数入栈,函数运行完毕之后再出栈;当前执行栈执行完之后在队列中取下一个任务,往复直到队列被清空。

任务队列的本质:

所有同步任务都在主线程上执行,形成一个执行栈;

主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。

一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

结合 macrotask(宏任务)和 microtash (微任务)的流程如下:

1、从任务队列中取出一个 macrotash 并执行;

2、检查微任务队列,执行并清空微任务队列;如果在执行的过程中,又有新的微任务,放在改执行队列的后边,然后也执行这一步,直到微任务被清空;

3、执行 ui render 操作,检查是否当前有存在渲染机会且需要渲染,如果需要,则开始执行各种渲染需要的工作,包括 animation frame callBacks,最后渲染 ui ,如果不需要则直接跳过;然后执行第一步,进行轮循,直到任务队列清空;

var taskMacLength = 10; // -----任务队列 
while(taskMacLength > 0){ // 执行栈     
	console.log('这是第',taskMacLength, "个任务");     
	taskMacLength--; 
}; 
console.log('任务执行完毕');
function fn(){        
	var a = 1;        
	setTimeout(function(){            
    var b = 2;            
    console.log('b', b);        
  }, 0)        
  console.log('a', a);    
}    
fn();    
var c = 3;    
console.log('c', c);        // a:1  c:3 b:2        
复制代码

主线程在执行代码的时候,当遇到 function 的时候,这时候只是函数的声明和定义,浏览器将其存放在堆栈中,但是并不会立即执行,因为此处仅仅是声明定义;接下来遇到 fn() , 从上下文中取出 fn 函数的代码进行编译,并创建该函数的执行上下文和可执行代码;在执行的过程中遇到 setTimeout, 主线程通知浏览器的其它线程程这个函数回调扔到浏览器的事件队列中(先进先出),等主线程其它的同步操作执行完再执行,接下来会执行 console.log('a', a) ; 然后跳出 fn, 往下执行,console.log('c', c); 等主线程当前队列清空,取出新的任务,执行 console.log('b', b);

事件队列一般包括三种:基本事件队列、事件句柄队列、待执行句柄队列;每个队列包含一个事件句柄指针,该指针指向该事件的事件句柄队列,每当需要通信的时候,由方法驱动事件按照所需要的事件种类,将事件对应的程序句柄以及优先级加入到时事件的待执行句柄队列中。

进程

进程(process)是浏览器正在运行的程序的实例;粗犷的讲是一个具有一定独立功能的程序关于某个数据集合的一次运动活动。进程是 CPU 资源分配的最小单位,也是最基本的一个分配单元;

浏览器的进程,以 chrome 为例,进程主要包括:Browser 进程、GPU 进程、第三方插件进程、渲染进程;

Browser 进程也可以称之为浏览器的主进程,主要作用:

1、负责浏览器界面的显示、与用户的交互;

2、负责各个页面的其它进程的管理、创建、销毁,比如子进程、GPU 进程等;

3、将渲染进程得到的内存中的位图(Bitmap)绘制到用户界面上;

4、网络资源的管理,比如下载等;

GPU 进程是用来处理图像的,包括其它进程发出的所有的 GPU 任务。GPU 进程会被分成不同的进程,渲染浏览器窗口中的不同部分;

第三方插件进程用来控制所有使用到的插件,比如 flash;每一种类型的插件对应一个进程;

渲染进程主要是作用于页面的渲染、脚本的执行、事件处理等等;主进程会根据选项卡的情况,创建多个渲染进程;

进程之间的通信采用 IPC(Inter-process Communication) 的方式;在握手上 IPC 其实和 TCP 比较类似,TCP 主要是网络传输层的通信;在网路层传输,见的比较多的,除了 TCP 外,还有 UDP。TCP 是面向链接的方式,虽然网络的不安全不稳定特性决定了多少次握手都不能保证连接的可靠性,但是 TCP 的三次握手在最低限度上保证了连接的可靠性。UDP 不是面向连接的,UDP 传输数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,当然也不用重发,所以说 UDP 是无连接的、不可靠的一种数据传输协议。

socket 是一个调用接口,不属于协议的范畴,一般情况下使用都是对 TCP 协议的一个封装,但是在创建 socket 的时候是可以指定使用的传输层协议的;socket 属于请求-响应形式,服务端可以主动的将消息推送给客户端。Socket 连接是一个长连接,理论上客户端和服务端一旦建立连接将不会主动断开此连接;

线程

线程(Thread)是 CUP 能够进行运算调度的最小单位;大部分情况下,都被包含在进程之中,是进程中的实际运作单位。在进程中,线程指的是进程中的一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务,同时这些线程还可以共享这个进程中的资源。

宏任务

宏任务(marcotask) 的本质是浏览器多个线程之间通信的一个消息队列;在 chrome 里,每个页面都对应一个进程,该进程又有多个线程,比如 js 线程、渲染线程、io 线程、网络线程、定时器线程等等,这些线程之间的通信是通过向对象的任务队列中添加一个任务(postTask) 来实现的。

浏览器的各种线程都是常驻线程,他们运行在一个 for 死循环里面,每个线程都有属于自己的若干任务队列,线程自己或者其它线程都可能通过 postTask 向这些任务队列添加任务,这些线程会不断的从自己的任务队列中取出任务执行,或者把处于睡眠状态直到设定的时间或者是有人 postTask 的时候把它唤醒。

能够建立宏任务的有: script、setTimeOut、setInterval、requestAnimationFrame、I/O、ui 渲染等。

微任务

微任务(microtask) 是确确实实存在的一个队列,microtask 是属于当前线程的,而不是其他线程 postTask 过来的任务。只是延迟执行了而已,比如 Promise.then、mutationObserve 都属于这种情况;

能够建立微任务的有:promise的回调、process.nextTick 、MutationObserver等;

文章分类
前端
文章标签