浏览器原理 14 # 消息队列和事件循环

说明

浏览器工作原理与实践专栏学习笔记

概念

下面介绍来自百科

进程

进程(Process 一段程序的执行过程)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

线程

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在 Unix System V 及 SunOS 中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

一个进程可以有很多线程,每条线程并行执行不同的任务。

第一版:线程一次执行

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

例子:

void MainThread(){
   int num1 = 1+2; //任务1
   int num2 = 20/5; //任务2
   int num3 = 7*8; //任务3
   print("最终计算的值为:%d,%d,%d",num1,num2,num3)//任务4
}

在这里插入图片描述

按照顺序在线程中依次被执行;等所有任务执行完成之后,线程会自动退出。

第二版:在线程中引入事件循环

在线程运行过程中处理新任务,所有的任务都是来自于线程内部的

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

通过一个 for 循环语句来监听是否有新的任务:可以在线程运行过程中,等待用户输入的数字,等待过程中线程处于暂停状态,一旦接收到用户输入的信息,那么线程会被激活,然后执行相加运算,最后输出结果。

//GetInput
//等待用户从键盘输入一个数字,并返回该输入的数字
int GetInput(){
    int input_number = 0;
    cout<<"请输入一个数:";
    cin>>input_number;
    return input_number;
}

//主线程(Main Thread)
void MainThread(){
	for(;;){
	    intfirst_num = GetInput()int second_num = GetInput();
	    result_num = first_num + second_num;
	    print("最终计算的值为:%d",result_num)}
}

在第一版的线程上做了两点改进:

  1. 引入了循环机制
  2. 引入了事件

在这里插入图片描述

第三版:线程模型:队列 + 循环

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

其他线程是如何发送消息给渲染主线程?

在这里插入图片描述

一个通用模式是使用消息队列

消息队列是一种数据结构,可以存放要执行的任务。它符合队列“先进先出”的特点,也就是说要添加任务的话,添加到队列的尾部;要取出任务的话,从队列头部去取。

在这里插入图片描述

改造方案:

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

在这里插入图片描述

1、构造一个队列

class TaskQueue{
  public:
  Task takeTask(); //取出队列头部的一个任务
  void pushTask(Task task); //添加一个任务到队列尾部
};

2、改造主线程,让主线程从队列中读取任务

TaskQueue task_queue;
void ProcessTask();
void MainThread(){
  for(;;){
    Task task = task_queue.takeTask();
    ProcessTask(task);
  }
}

3、发送任务让主线程去执行

Task clickTask;
task_queue.pushTask(clickTask)

跨进程发送消息

处理其他进程发送过来的任务

如何处理其他进程发送过来的任务?

渲染进程专门有一个 IO 线程用来接收其他进程传进来的消息,接收到消息之后,会将这些消息组装成任务发送给渲染主线程,后续跟处理其他线程发送过来的任务一样。

在这里插入图片描述

消息队列中的任务类型

参考资料:【Chromium 的官方源码】

内部消息类型

  1. 输入事件(鼠标滚动、点击、移动)
  2. 微任务
  3. 文件读写
  4. WebSocket
  5. JavaScript 定时器等等

与页面相关的事件

  1. JavaScript 执行
  2. 解析 DOM
  3. 样式计算
  4. 布局计算
  5. CSS 动画等

如何保证页面主线程安全退出

Chrome 是这样解决的,确定要退出当前页面时,页面主线程会设置一个退出标志的变量,在每次执行完一个任务时,判断是否有设置退出标志。

TaskQueue task_queue;
void ProcessTask();
bool keep_running = true;
void MainThread(){
  for(;;){
    Task task = task_queue.takeTask();
    ProcessTask(task);
    if(!keep_running) //如果设置了退出标志,那么直接退出线程循环
        break; 
  }
}

页面使用单线程的问题

第一个:如何处理高优先级的任务

一个典型的场景

监控 DOM 节点的变化情况(节点的插入、修改、删除等动态变化),然后根据这些变化来处理相应的业务逻辑。如果 DOM 发生变化,采用同步通知的方式,会影响当前任务的执行效率;如果采用异步方式,又会影响到监控的实时性

消息队列机制并不是太灵活,为了适应效率和实时性,此时微任务就应用而生。通常把消息队列中的任务称为宏任务。每个宏任务中都包含了一个微任务队列

微任务是如何解决执行效率的问题?

在执行宏任务的过程中,如果 DOM 有变化,那么就会将该变化添加到微任务列表中,这样就不会影响到宏任务的继续执行。

微任务是如何解决实时性的问题?

等宏任务中的主要功能都直接完成之后,渲染引擎并不着急去执行下一个宏任务,而是执行当前宏任务中的微任务。

第二个:如何解决单个任务执行时长过久的问题

单个任务执行时间过久:

在这里插入图片描述

JavaScript 可以通过回调功能来规避这种问题,也就是让要执行的 JavaScript 任务滞后执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凯小默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值