多线程到Event Loop

开发·学习 · 2023-10-18 · 53 人浏览

CPU、进程、线程

CPU可以类比为一个工厂,进程就相当于工厂的车间,而线程则是车间的工人。

CPU总是会运行一个进程,其他进程处于非运行状态,而一个进程又可以包括多个线程,多个线程共享进程的资源。

进程是CPU资源分配的最小的单位,线程是CPU调度的最小单位。单线程和多线程都是指在一个进程中的单与多。

浏览器与进程

对计算机来说,每一个程序都是一个进程,一个程序通常会有很多功能模块对应的就是子进程, 而通过子进程实现的应用程序就是所谓的多进程,如浏览器,每一个Tab就是一个进程:
2023-10-18T02:33:02.png

浏览器的进程

  • 主进程
    -- 协调子进程、如创建、销毁
    -- 界面显示、交互
    -- 网络请求、文件访问
    -- 将渲染进程的内容绘制到用户界面
  • 第三方插件进程
    -- 每个插件会对应一个进程,使用时创建
  • GPU进程
    -- 绘制相关
  • 渲染进程(浏览器内核)重点
    -- 负责页面渲染、脚本执行、事件执行
    -- 每个Tab页面对应一个渲染进程

渲染进程

由进程和线程一对多的关系,然后梳理下该进程下包含哪些线程

  • GUI渲染线程
    -- 负责页面渲染、绘制
    -- 页面重绘回流时执行
    -- 与JS引擎线程互斥,防止渲染结果不可控制
  • JS线程
    -- 处理解析JS
    -- 只有一个线程(所谓的JS单线程)
    -- 与GUI渲染互斥,防止渲染结果不可控制
  • 事件触发线程
    -- 控制事件渲染
    -- 将事件放入JS引擎所在的执行队列
  • 定时器线程
    -- setTimeoutsetInterval
    -- 定时任务不是由JS引擎计时,而是由定时器线程来工作
    -- 通知事件触发线程
  • 异步线程
    -- 单独线程处理AJAX请求
    -- 完成时将回调交给事件触发线程

为什么JS是单线程?

早期硬件支持不行,且因为多线程的复杂性(加锁、编码复杂性),如果同时操作DOM,会导致DOM渲染结果不可预期(核心原因)

为什么渲染和JS互斥?

归根结底就是因为JS可以操作DOM,修改元素属性和渲染界面同时运行,那么最终渲染前后的元素可能就不同了。

Event Loop机制

  1. JS分同步任务异步任务
  2. 同步任务都在JS线程执行,形成执行栈
  3. 事件触发线程会管理一个任务队列,异步任务的回调也会放入这个任务队列中
  4. 当同步任务执行栈完成后,JS引擎线程空闲,系统会调度任务队列,将异步任务回调添加到执行栈中执行

setTimeout/setInterval/XHR/Fetch等 代码本身是同步任务,而其中的回调才是异步任务。

setTimeut/setInterval 则是由定时器线程计时,XHR/Fetch由异步线程管理, 最终都是交给事件触发线程管理的任务队列中

当同步任务执行完成后,JS引擎线程会访问事件触发线程,任务队列中是否有需要执行的回调函数,如果有就会交给JS引擎线程去执行

console.log('1)

setTimeout(()=>console.log(2))

console.log('3')

//1 3 2

宏任务

我们可以将任务队列中的任务分为宏任务与微任务。 通过JS引擎线程执行的主代码栈我们可以看作是一次宏任务。
并且为了能够使宏任务和DOM任务有序进行,通常在一个宏任务执行后,下一个宏任务执行前,GUI渲染开始工作对页面进行渲染。

注意,如setTimeout、setInterval也算是宏任务
document.body.style = 'background:blue';
setTimeout(()=>{
  document.body.style = 'background:black';
})

这里你会发现,页面会先变成蓝色,然后立马变成黑色,这是因为代码块执行完成后(即第一个宏任务执行),GUI线程工作,渲染页面为蓝色, 跟着下一个宏任务(setTimeout) 页面变为黑色

微任务

除了宏任务,还存在微任务,代表如:Promise、nextTick、queueMicrotask
微任务的特点就是在宏任务结束后,下一个宏任务执行前, 立马进行执行的任务。(同理它也是在GUI渲染线程执行前的任务队列)

setTimeout(() => console.log(2));

Promise.resolve().then(() => {
  console.log(3);
});

setTimeout(() => {
  Promise.resolve().then(() => console.log(5));
  console.log(4);
});
console.log(1);
// 1 3 2 4 5

梳理上面代码的执行过程, 首先是主代码块的同步任务执行(宏任务),输出1, 接着同时遇到下一个宏任务和微任务setTimeout/Promise,此时由于处理微任务,输出3, 微任务执行完成后,没有其他微任务后,再执行宏任务,输出先进入任务队列的 2, 接着执行剩下的宏任务,此时这个宏任务内,会先将微任务加入微任务队列,由于存在同步代码console.log(4)会先执行,执行完成后,再去任务队列执行剩下的微任务

梳理

  • 执行宏任务(如果执行栈中没有就去任务队列获取)
  • 先处理遇到的微任务,添加到任务队列中
  • 宏任务执行完成后,先执行微任务队列的所有微任务
  • 微任务完成后再执行宏任务,宏任务结束后开始GUI线程渲染
  • GUI渲染完成后,JS线程接管开始下一个宏任务

2023-10-18T03:20:14.png

Event Loop 浏览器内核
Theme Jasmine by Kent Liao