消息队列和事件循环是如何让页面运行的?

8/23/2022 Event LoopQueue

# 前言

渲染进程中有一个主线程,主线程处理的任务有很多:

  • 解析DOM和结构
  • 计算CSS样式
  • 绘制布局
  • 处理JavaScript任务
  • 用户事件输入
  • more...

如何让这些不同类型的任务在主线程中有序的执行,浏览器采用消息队列 + 事件循环系统来对任务进行统筹调度。

# 单线程执行任务

最基础的场景:有一系列的任务,我们把所有任务代码顺序写入主线程,这些任务在主线程中按照顺序依次执行,任务执行完毕,线程自动退出。

# 线程运行的过程中处理新任务

线程运行的过程中,如何去处理接收or产生的新任务,我们此时就采用事件循环机制来解决。
场景:页面上的Input框,我们需要等待用户输入,拿到输入结果进行处理?

  • 将用户输入的操作定义为事件。
  • 引入循环机制,for循环去监听事件。线程暂停等待新任务出现然后执行。

该场景下的任务都是来自线程内部,无法处理另一个线程去让主线程执行一个任务的情况。

# 处理其他线程发送的任务

渲染进程中,主线程经常会接受IO线程的任务:

  • 资源加载完毕,主线程需要开始进行DOM解析
  • 鼠标点击,主线程需要执行相应JavaScript脚本处理点击事件 渲染主进程中线程通信 使用队列这种数据结构设计来解决处理其他线程发送的任务。
  • 创建一个消息队列
  • IO线程中产生的新任务依次添加到消息队列尾部
  • 主线程循环从消息队列首部读取执行任务
  • 只要消息队列中有任务,主线程就会去执行
  • 其他线程想发送任务让主线程执行,只需要想消息队列添加任务

注意:这样会导致多个线程操作一个消息队列,所以添加任务,取出任务需要加上同步锁消息队列

# 处理其他进程发送的任务

使用消息队列,实现线程之间通信,如何实现进程之间任务通信? 渲染进程中的IO线程就是用来接收其他进程发送的任务,接收到别的进程的任务,将这些任务组装发送给消息队列。

# 消息队列中的任务类型

  • 页面相关的事件 => JavaScript执行、解析DOM、样式计算、布局计算、CSS动画...
  • 输入事件(鼠标滚动、点击、移动)
  • 微任务
  • 文件读写
  • WebSocket
  • JavaScript 定时器
  • more...

以上这些任务都会在主线程中执行,平时开发中,我们需要衡量这些事件占用的时间,解决单个任务占用主线程过久

# 执行完毕退出主线程

Chrome确定要退出当前页面,页面主线程会设置一个退出标识变量,每次执行完一个任务,判断是否有无退出标识的变量,如果有退出标识的变量,中断当前的所有任务,退出线程

# 页面使用单线程的缺陷

  1. 如何处理高优先级任务 场景:监控DOM节点变化情况(插入、修改、删除),根据变化处理业务逻辑。
    一般设计方案:利用JavaScript设计一套监听接口,每当DOM发生变化,渲染引擎同步调用接口,这属于观察者模式

问题:DOM变化十分频繁,每次发生变化,直接同步调用JavaScript接口,那么当前的任务执行事件会被拉长,导致执行效率下降。如果把DOM变化设计成异步的消息队列,将DOM变化添加到消息队列,这又会影响监控的实时性,有可能DOM任务前面有很多任务在排队。

如果DOM发生变化:

  • 采用同步通知的方式,会影响当前任务的执行效率。
  • 如果采用异步方式,又会影响到监控的实时性。 如何权衡效率和实效性,设计出了微任务: 消息队列中的任务称为宏任务,每个宏任务中都包含了一个微任务队列,在执行宏任务的过程中,如果DOM有变化,那么就会将该变化添加到微任务队列中,这样就不会影响到宏任务的继续执行,解决了执行效率的问题。 当宏任务中的功能执行完毕,渲染进程并不会立刻去执行下一个宏任务,而是先去执行当前宏任务中的微任务队列,因为DOM变化的事件都保存在这些微任务队列中,这样也就解决了实时性问题。
  1. 单个任务执行时长过久
    所有任务都在单线程中执行的,每次只能执行一个任务,其他任务处于等待状态。
    场景:执行动画过程中,如果其中有个JavaScript任务执行时间过久,占用了动画单帧的时间,这样会给用户制造了卡顿的感觉。针对这种情况,JavaScript可以通过回调功能来规避这种问题,也就是让要执行的JavaScript任务滞后执行

# 总结

  • 解决线程执行过程中处理新任务,引入循环和事件系统
  • 解决线程之间互相通信,引入消息队列
  • 进程发送任务给渲染进程,通过IPC把任务发送给渲染进程的IO线程,然后IO线程把任务发送到消息队列,主线程接受到任务。
  • 解决消息队列机制不灵活,兼顾效率和时效性,引入宏任务、微任务队列的概念。
  • 这种消息队列的机制,主要解决的就是进程线程之间互相通信问题,所以才称之为消息队列的原因吧。
Last Updated: 8/24/2022, 6:38:23 PM