浏览器工作原理(15) - 消息队列和事件循环

上一篇文章讲了V8引擎编译器和解释器,了解了V8是如何执行一段代码的,接下来的文章再着重分析一下浏览器中的页面循环系统

浏览器每个渲染进程都有一个主线程,主线程负责处理DOM,计算样式,处理布局,还需要执行JavaScript代码以及交互,显得非常的繁忙。那么这么多的任务,主线程是怎么按顺序执行的呢?这时候就要有一个系统来专门负责调度这些任务,这个系统就是今天的主角 – 消息队列和事件循环系统

使用单线程处理安排好的任务

比如有一下几个任务:

  • 任务 1:1+2
  • 任务 2:20/5
  • 任务 3:7*8
  • 任务 4:打印出任务 1、任务 2、任务 3 的运算结果

把所有任务代码按照顺序写进主线程里,等线程执行时,这些任务会按照顺序在线程中依次被执行;等所有任务执行完成之后,线程会自动退出。可以参考下图来直观地理解下其执行过程:
在这里插入图片描述

在线程运行过程中处理新任务

但并不是所有的任务都是在执行之前统一安排好的,大部分情况下,新的任务是在线程运行过程中产生的。比如在线程执行过程中,又接收到了一个新的任务要求计算“10+2”,那上面那种方式就无法处理这种情况了。

要想在线程运行过程中,能接收并执行新的任务,就需要采用事件循环机制

可以通过一个 for 循环语句来监听是否有新的任务

这次的执行机制中,我们加入了一个循环,另外还引入了另一个事件(等待输入并执行),通过引入事件循环机制,就可以让该线程“活”起来了,我们每次输入两个数字,都会打印出两数字相加的结果,你可以结合下图来参考下这个改进版的线程
在这里插入图片描述

处理其他线程发过来的任务

之前的示例,所有任务都来自线程内部,如果另外一个线程想要让主线程执行一个任务,那么该怎么办呢?一起看一下其他线程是如何发送消息给渲染主线程的,参考下图:
在这里插入图片描述
从上图可以看出,渲染主线程会频繁接收到来自于 IO 线程的一些任务,接收到这些任务之后,渲染进程就需要着手处理,比如接收到资源加载完成的消息后,渲染进程就要着手进行 DOM 解析了;接收到鼠标点击的消息后,渲染主线程就要开始执行相应的 JavaScript 脚本来处理该点击事件。

那么如何设计好一个线程模型,能让其能够接收其他线程发送的消息呢?一个常用模式就是使用消息队列

消息队列

什么是消息队列?
在这里插入图片描述
消息队列是一种数据结构,可以存放要执行的任务。它符合队列“先进先出”的特点,也就是说要添加任务的话,添加到队列的尾部;要取出任务的话,从队列头部去取。

现在改变一下上面的执行方式:

在这里插入图片描述
从上图可以看出,我们的改造可以分为下面三个步骤:

  1. 添加一个消息队列;
  2. IO 线程中产生的新任务添加进消息队列尾部;
  3. 渲染主线程会循环地从消息队列头部中读取任务,执行任务。
处理其他进程发送过来的任务

现在我们实现了跨线程执行任务,那么如何处理跨进程的任务呢?

在这里插入图片描述
从图中可以看出,渲染进程专门有一个 IO 线程用来接收其他进程传进来的消息,接收到消息之后,会将这些消息组装成任务发送给渲染主线程,后续的步骤就和前面讲解的“处理其他线程发送的任务”一样了

消息队列中的人物类型

任务类型主要有:输入事件、微任务、文件读写、定时器等,另外还有JavaScript执行,DOM解析,样式计算,布局计算等

这些任务事件都是在主线程中执行,所以在开发web应用时,需要思考一下事件所占用的时长,尽量避免单个任务占用主线程时间过长的问题。

安全退出

页面主线程执行完成之后,页面主线程会设置一个退出标志,每次执行完一个任务时,会判断是否有退出标志,有的话就可以退出当前线程了

页面使用单线程的缺点

上面阐述了页面是如何使用单线程进行工作的,那么单线程有什么特点呢?

从上面的分析可以知道,页面线程所有任务都来自消息队列,消息队列是“先进先出”的,也就是说后面的任务要等到前面的任务执行完 才能执行,那么这个原则会有下面两个问题需要面对:

1、如何处理优先级较高的任务

例如有下面的一个场景:监控DOM节点的变化情况,根据DOM变化情况来处理相应的业务逻辑,一个通用的设计是利用JavaScript做一个监听接口,当DOM发生变化时,渲染引擎调用这个接口执行处理业务逻辑,这就是观察者模式

不过如果DOM变化非常频繁,每次发生变化的时候,都直接调用相应的事件,那么这个任务会一直存在,后面的任务一直不能进行处理,导致 执行效率下降

如果采用异步的方式,放在队列的最后,那么 实时性就受影响

针对这种情况,微任务 的概念就应运而生了, 一起看一下微任务是如何处理效率和实时性的问题

我们把消息队列的任务称为 宏任务, 每个宏任务都包含了一个微任务队列, 在执行宏任务的过程中,如果DOM发生变化,那么就会将该变化添加都微任务列表中,这样不会影响宏任务的继续执行,从而能解决执行效率的问题。

宏任务的主要功能都执行完之后,渲染引擎不会着急执行下一个宏任务,而是执行当前宏任务的微任务,这样就可以解决实时性的问题

如何解决单个执行任务过长的问题

因为所有的任务都是在单线程中执行,所以每次只能执行一个任务,其他任务就必须等待,如果前面的任务执行很久,那么后面的都要一直等待下去

在这里插入图片描述上图中可以看出,如果一个JavaScript任务执行过久,会给用户造成一种卡顿的感觉,这种情况通常会使用回调的方式来处理。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值