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

简介: 浏览器原理 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
}

2021041209311081.png


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



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

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



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


通过一个 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. 引入了事件

20210412094541997.png


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


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

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

20210412095135291.png


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

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


20210412095303989.png



改造方案:


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


20210412100232342.png


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 线程用来接收其他进程传进来的消息,接收到消息之后,会将这些消息组装成任务发送给渲染主线程,后续跟处理其他线程发送过来的任务一样。


20210412112225125.png


消息队列中的任务类型

参考资料:【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 有变化,那么就会将该变化添加到微任务列表中,这样就不会影响到宏任务的继续执行。


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

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


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

单个任务执行时间过久:

20210412120332641.png

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


目录
相关文章
|
1月前
|
存储 缓存 前端开发
浏览器缓存工作原理是什么?
浏览器缓存工作原理是什么?
|
30天前
|
Web App开发 JavaScript 前端开发
浏览器与Node.js事件循环:异同点及工作原理
浏览器与Node.js事件循环:异同点及工作原理
|
10天前
|
JavaScript 前端开发 网络协议
浏览器的工作原理
主要分为导航、获取数据、HTML解析、css解析、执行javaScript、渲染树几个步骤。
16 1
|
1月前
|
存储 安全 前端开发
浏览器跨窗口通信:原理与实践
浏览器跨窗口通信:原理与实践
95 0
|
1月前
|
消息中间件 JavaScript 前端开发
前端秘法进阶篇----这还是我们熟悉的浏览器吗?(浏览器的渲染原理)
前端秘法进阶篇----这还是我们熟悉的浏览器吗?(浏览器的渲染原理)
|
1月前
|
存储 前端开发 JavaScript
揭秘浏览器的事件循环:让网页动起来的幕后英雄
揭秘浏览器的事件循环:让网页动起来的幕后英雄
揭秘浏览器的事件循环:让网页动起来的幕后英雄
|
1月前
|
消息中间件 前端开发 Java
【面试题】前端必修-浏览器的渲染原理
【面试题】前端必修-浏览器的渲染原理
|
6月前
|
Web App开发 JavaScript 前端开发
从浏览器原理出发聊聊Chrome插件
本文从浏览器架构演进、插件运行机制、插件基本介绍和一些常见的插件实现思路几个方向聊聊Chrome插件。
867 0
|
9月前
|
安全 算法 网络协议
浏览器基础原理-安全: HTTPS
浏览器基础原理-安全: HTTPS
65 0
|
9月前
|
Web App开发 存储 监控
浏览器基础原理-安全: 渲染进程-安全沙盒
浏览器基础原理-安全: 渲染进程-安全沙盒
41 0

热门文章

最新文章