• 微信公众号:美女很有趣。 工作之余,放松一下,关注即送10G+美女照片!

Event Loop

开发技术 开发技术 6小时前 3次浏览

Event Loop

一、线程与进程

  • JS 是单线程执行的,指的是一个进程里只有一个主线程

1.1.概念

  • 进程是 CPU资源分配的最小单位;线程是 CPU调度的最小单位
  • 进程好比工厂,有单独的专属自己的工厂资源
  • 线程好比工人,多个工人在一个工厂中协作工作,工厂与工人是 1:n的关系。也就是说一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
  • 多个工厂之间独立存在

1.2.多进程与多线程

  • 多进程:在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。
    • 多进程带来的好处,比如你可以听歌的同时,打开编辑器敲代码,编辑器和听歌软件的进程之间丝毫不会相互干扰。
  • 多线程:程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
  • 以Chrome浏览器中为例,当你打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程,比如渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。

1.3.单线程

  • 一只有当上一个任务完成之后才会继续完成下一个任务。==>同步操作的概念。
  • 带来的问题:如果前一个任务耗时很长,后一个任务就得一直等着。这就会导致I/O操作(耗时但cpu闲置)时造成性能浪费的问题。
  • 解决:异步操作,可以一起执行多个任务。主线程完全可以不管IO操作,暂时挂起处于等待中的任务,先运行排在后面的任务。等到I/O操作返回了结果,再回过头,把挂起的任务继续执行下去。
  • I/O:将数据读入内存或者内存输出的过程

二、浏览器内核

  • 浏览器内核(渲染引擎):是通过取得页面内容、整理信息(应用CSS)、计算和组合最终输出可视化的图像结果。

  • 浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:

    • GUI 渲染线程
    • JavaScript引擎线程
    • 定时触发器线程
    • 事件触发线程
    • 异步http请求线程

2.1.GUI渲染线程

  • 主要负责页面的渲染,解析HTML、CSS,构建DOM树,布局和绘制等。
  • 当界面需要重绘或者由于某种操作引发回流时,将执行该线程。
  • 该线程与JS引擎线程互斥,当执行JS引擎线程时,GUI渲染会被挂起,当任务队列空闲时,主线程才会去执行GUI渲染。

2.2.JS引擎线程

  • 主要负责处理 JavaScript脚本,执行代码。
  • 也是主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待 JS引擎线程的执行。
  • 当然,该线程与 GUI渲染线程互斥,当 JS引擎线程执行 JavaScript脚本时间过长,将导致页面渲染的阻塞。
var x = true;
while(x){//死循环,会阻塞进程
   console.log(x);
};
console.log("don't carry out");    //不会执行

2.3.定时器触发线程

  • 负责执行异步定时器一类的函数的线程,如: setTimeout,setInterval。
  • 主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待JS引擎线程执行。

2.4.事件触发线程

  • 主要负责将准备好的事件交给 JS引擎线程执行。
  • 比如 setTimeout定时器计数结束, ajax等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待 JS引擎线程的执行。

5.异步http请求线程

  • 负责执行异步请求一类的函数的线程,如: Promise,axios,ajax等。
  • 主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待JS引擎线程执行。

三、浏览器中的Event Loop

3.1.执行栈和任务队列

执行栈

  • 一个专门用来存放执行代码的栈内存结构,执行的主线程

任务队列

  • 当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为任务队列。
  • 需要注意的是异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。

3.2.同步任务和异步任务

  • JS是单线程的,可以将所有任务分成两种,一种是同步任务,另一种是异步任务。

同步任务

  • 在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。

异步任务

  • 不进入主线程,而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

3.3.macro-task和micro-task

  • 异步任务有两种:macro-task(宏任务)和micro-task(微任务)
  • 宏任务主要包含:setTimeout、setInterval、script(整体代码)、 I/O 操作、UI 渲染等。
  • 微任务主要包含:Promise、MutaionObserverhtml(html5新特性)

3.4.Js异步执行的运行机制

  1. 所有同步任务都在主线程上执行,形成一个执行栈。
  2. 主线程之外,还存在一个任务队列。只要异步任务有了运行结果,就在任务队列之中放置一个事件。
  3. 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  4. 主线程不断重复上面的第三步。

3.5.Event Loop 过程解析

  • 一开始执行栈空,macro 队列里有且只有一个 script 脚本(整体代码)。

  • 全局上下文(script 标签)被推入执行栈,同步代码执行。在执行的过程中,会判断是同步任务还是异步任务,通过对一些接口的调用,可以产生新的 macro-task 与 micro-task,它们会分别被推入各自的任务队列里。

  • 同步代码执行完了,script 脚本会被移出 macro 队列,调用栈Stack会清空。

  • 执行所有的微任务。逐个执行队列中的任务并把它出队,直到队列被清空。

  • 上述过程循环往复,直到两个队列都清空

  • 注:宏任务是一个一个的。微任务是一队一队的。只有微任务队列清空才执行宏任务

经典例子:

console.log(0);
setTimeout(function() {//t1-宏任务
    console.log(1)
}, 0);
Promise.resolve().then(() => {//t2-微任务
console.log(2)
});
console.log(3)
//打印顺序是 0 3 2 1
  1. script 任务先运行。首先输出 0,遇到Promise实例then微任务,setTimeout宏任务。此时 macro-task有 t1,micro-task有t2,继续执行输出3。
  2. 执行所有的微任务t2,输出 2
  3. 执行宏任务任务t3,输出1
  4. 队列空,执行完毕

复杂一点的例子:

new Promise(resolve => {
    resolve(1);

    Promise.resolve().then(() => { // t2-微任务
        console.log(2)
    });
    setTimeout(function(){//t3-宏任务
        console.log(3);
        Promise.resolve().then(()=>{//t5-微任务
            console.log('t5')    
        })
    },0)
    console.log(4)
}).then(t => {//t1-微任务
    console.log(t)
});
setTimeout(function(){//t4-宏任务
    console.log(5);
},100)
console.log(6);

/*
script执行:t1-微队列,t2-微任务,t3-宏任务,输出4,t4-宏任务,输出6
==>队列:微-> t2-微队列,t1-微任务
        宏-> t3-宏任务,t4-宏任务
        
执行所有的微任务t2、t1:输出2,输出1 
(注:先执行t2、再执行t1,resolve是用来表示promise的状态,只有promise调用then的时候,then里面的函数才会被推入微任务中。)
==>队列:微-> 
        宏-> t3-宏任务,t4-宏任务
        
执行t3-宏任务:输出3,t5-微任务
==>队列:微-> t5-微任务
        宏-> t4-宏任务
        
//执行所有的微任务t5:输出t5 
==>队列:微-> 
        宏-> t4-宏任务
        
        
执行t4-宏任务:输出5
==>队列:微-> 
        宏-> 


最后输出结果是:4,6,2,1,3,t5,5
*/

参考自:

Js事件循环(Event Loop)机制


程序员灯塔
转载请注明原文链接:Event Loop
喜欢 (0)