异步与资源调度 以浏览器事件循环为例

news/2025/3/12 9:52:13/文章来源:https://www.cnblogs.com/ColaBlack/p/18549078

初次发布于我的个人文档

参考:

chromiun官方文档

w3c官方文档

针对一个异步的程序应该如何对它进行资源的调度呢?本文以浏览器为典型范例进行简单介绍。

1.查看浏览器的多进程图景

打开任意一个浏览器这里以edge为例。

然后打开Windows的任务管理器,你看到的可能是这样:

Windows任务管理器截图

事实上,在edge浏览器(其他浏览器也有类似的功能)按shift+esc键能打开浏览器内部的任务管理器,可能长这样:

浏览器任务管理器不管怎么样总之,都可以看到你打开了一个浏览器实际上打开了好多个“进行中的程序”也就是进程。

浏览器作为及其复杂的而又非常常用的程序,不得不使用多进程的方式优化。

而正是因为浏览器使用了多进程,所以有的时候你会发现,某一个网页卡了但是浏览器没有卡死,一个页面卡死了但是另一个页面没有卡死。这是因为他们本来就“不是一个程序”。

多进程是一种充分利用计算机硬件资源的方式,关于多进程、多线程和协程的有关概念以后有时间也许会分享。

总而言之言而总之,浏览器是一个多进程的复杂的应用程序。

浏览器的诸多线程里,最主要的是三个:

  • 浏览器进程负责浏览器界面的展示(不是网页,而是浏览器界面内的什么选项卡啊按钮啊什么的)、用户交互、子进程管理等等

  • 网络进程负责启动多个线程来执行网络任务,也就是收发各种网络请求。

  • 渲染进程才是负责网页渲染的,有时间也会展开说说浏览器是如何渲染网页的。
    渲染进程是浏览器最重要也是最繁忙的进程,说他重要是因为我打开浏览器最主要的就是想看页面啊,没渲染进程怎么能行?说他繁忙,可以等以后展开。

2.阻塞和非阻塞、同步和异步

刚刚我们说渲染进程是浏览器最重要也是最繁忙的进程,那么渲染进程是怎么组织资源调度和分配的呢?

比如说,用户点击了一个按钮,我肯定要执行一段代码,但是与此同时可能有一个计时器也刚好到时间了,也想执行一段代码,那怎么办呢?

进一步的,我怎么知道用户点击了一个按钮呢?是不是我得一直监听用户输入啊,那我岂不是要开一个任务一直运行着,那我渲染进程岂不是还得等用户点击按钮用户有输入了才能继续执行?

这种现象被称为阻塞。

也就是这个任务会卡着某一个线程,这个线程要等这个任务完成才能继续执行代码。

我们写的一般的代码是不会卡着线程的,比如什么i++啊之类的,一下子就完成了所以不会阻塞。

那如果我想做一些网络交互啊,磁盘输入输出(input/output,IO)之类的,那时间就长了,主线程就得等这些任务完成才能继续执行了,这就是阻塞了。

再比如,下面的Python代码

a = "hello World"
input("请输入")
print(a)

第二行在等待用户输入,如果用户一直不输入那么程序不会停止也不会运行第三个语句,而是会一直等待用户输入,用术语来讲就是阻塞。

像这种,每个任务都按顺序完成的情况我们称之为同步。

根据刚刚举的例子可以发现,同步的代码可能发生阻塞也可能不发生,我们分别称这两种情况为同步阻塞、同步非阻塞。

结论:同步不一定阻塞

不过比起这个结论更重要的是理解刚刚说的分析的过程。

对于浏览器来说,他确实想执行监听用户的任务,但是又不想阻塞主线程不然用户看到的网页就一卡一卡的。

那该怎么办呢?

那就要使用异步的方式。

这是一个简单的用java(python由于全局解释器锁的存在不太适合当例子了,JavaScript则是运行在浏览器的渲染主线程上也不太适合)实现的异步调用demo。

public class CallbackThreadExample {// 定义一个回调接口interface Callback {void onFinish(String result);}// 实现Runnable接口的类static class Task implements Runnable {private final Callback callback;public Task(Callback callback) {this.callback = callback;}@Overridepublic void run() {// 执行一些任务try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}// 任务完成,调用回调函数if (callback != null) {callback.onFinish("任务完成");}}}public static void main(String[] args) {// 创建一个回调对象Callback callback = result -> System.out.println("回调函数被调用,结果:" + result);// 创建并启动新线程Thread thread = new Thread(new Task(callback));thread.start();// 主线程可以继续执行其他任务System.out.println("主线程继续执行...");}
}

callback被称为回调函数,简单说就是“回头再调用"。

main函数里定义了回调函数 Callback callback = result -> System.out.println("回调函数被调用,结果:" + result);

就是说等时机成熟,你给我调用这句输出语句。

task类定义了一个任务,叫线程睡眠2秒,然后再调用回调函数。(你看,回调函数是不是“回头再调用了”)

很显然,task任务会阻塞主线程2秒,我们不希望这件事发生。所以新开了另一个线程来执行这个任务,这样主线程就可以继续执行了,也就不会阻塞了。

这就是异步非阻塞

创建新线程执行原本会阻塞的任务,利用回调函数给予反馈是异步的一种实现方式。前面我们说同步的程序所有任务会按顺序完成,但这里异步的任务会和主线程同时完成,这就是异步和同步的区别。

3.消息队列

那么浏览器是怎么处理纷繁复杂的异步任务的呢?

熟悉JavaScript的话你会发现,像网络IO,交互,计时器等都是也只能按异步+回调的方式调用。那浏览器会在什么时候执行回调呢?

如果网络IO完成的同时,计时器时间也到了应该先完成哪个?

很快你会发现,这是一个任务生产的速率大于任务消费的速率的情况,这种模型我们一般可以通过排队的方式解决。

你们俩同时想我启用某个任务了是吧,一个任务正在进行另外两个任务也想启动是吧,排队!

队列这种数据结构就是现实里的排队,讲究的是先到先得

在我们这种情况下,用面向对象的术语来说就是需要一个消息队列,当一个任务想执行了,先往消息队列里发一个消息,我想执行某某任务,然后渲染主线程会先服务队首也就是排在最前面的人。

4.事件循环

在浏览器具体实现的时候,又有一些细节需要注意。

我们以谷歌chromiun内核为例,观察chromiun的源码,你会发现在chromiun渲染进程中存在一个死循环(这个循环被W3C称为事件循环在chromiun源码中被称为消息循环),它不断地从消息队列中取任务,当消息队列为空时会休眠,只要队列里有任务就执行队首的任务,是吗?

是也不完全是。

对于一般的简单程序来说也许这样就足够了,但是对浏览器这个复杂的应用程序来说,完全不够!

浏览器中有事件交互、网络IO等诸多异步任务,很显然事件交互的优先级要高一些,也就是当用户点击按钮啊什么的你浏览器必须尽快给出响应,不要让用户觉得卡顿。

但是这样的话就破坏了消息队列“先进先出,先到先得”的特性,又该怎么办呢?

5.微队列和宏队列

动动你的脑瓜子想想,虽然队列里的成员不能有优先级,只能先到先得,但是消息队列不是可以有优先级吗?

我开好几个不同优先级的消息队列不就得了。

如果你看过很多早期的教程或者早期w3c规范,你可能会听说过微队列和宏队列的说法,但是随着浏览器执行任务的复杂,w3c已经不再使用宏队列的说法了。(微队列仍在使用)光靠微队列和宏队列已经不足以支撑现代浏览器的资源调度了。

现代浏览器有微队列、交互队列、延时队列等诸多消息队列。

按照w3c最新的规范,微队列是优先级最高的队列,当渲染主进程完成手头现有的工作后只要微队列有任务在等着,那么他就会执行微队列的任务。这是因为微队列里都是一些支撑浏览器运行的重要任务。交互队列由于和用户体验息息相关所以优先级也比较高。像延时队列这种只关乎计时器的消息队列优先级就比较低,如果微队列的任务和交互队列的任务没有完成,那即使计时器到了,计时器的回调函数也不会被执行。这也是为什么计时器其实不能严格准确精准无误地按照程序员设定的时间执行任务

前面我们说“微队列里都是一些支撑浏览器运行的重要任务”其实也并不完全吧,我们还是有办法把一个函数添加到微队列的,可以通过以下代码

Promise.resolve().then(函数)

将函数包装成任务,塞到微队列。

翻看chromiun内核源码你会知道,这些消息队列里存放的不是函数句柄(或者说指向函数的指针)而是一个被包装起来的结构体,所以这里要用“包装成任务”的说法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/841007.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

dl dt dd 标签【示例】

代码示例 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body> <dl><dt>HTML</dt><dd>HyperText Markup Language,是一种用于…

【Azure 环境】从Azure AD(Entra ID)中获取的Token在访问自定义的接口应用时验证不通过

“IDX10503: Signature validation failed. Keys tried: Microsoft.IdentityModel.Tokens.X509SecurityKey , KeyId: xxxxxxxxxxxxxxxxxxx”问题描述 集成Azure AD(新名称Microsoft Entra ID),拿到对应的accessToken作为登录用的凭证。这个 AccessToken 能够正常返回,并作为M…

多平台数据集成的实践案例:吉客云到MySQL

测试-查询销售渠道信息(已删除数据)-dange:吉客云数据集成到MySQL的技术案例分享 在现代企业的数据管理中,如何高效、可靠地实现多平台间的数据集成是一个关键问题。本次我们将聚焦于一个具体的系统对接案例,即将吉客云中的销售渠道信息(包括已删除数据)集成到MySQL数据库…

简洁的bootstrap垂直tab选项卡

这是一款简洁的bootstrap垂直tab选项卡。该选项卡在bootstrap原有的选项卡插件的基础上,通过添加一些css样式,将它显示为垂直的选项卡。演示 下载使用方法 在页面中引入下面的文件。<link rel="stylesheet" href="https://cdn.bootcss.com/twitter-boots…

会计科目-5大基础科目(资产,损益类-负债-所有者权益-成本)

资成费,负所收”方;负债、 有借必有贷,借贷必相等会计科目本质:资产类 负债类 损益类所有者权益成本类

[Linux]简单的shell实现

一个简单的shell实现 什么是内建命令 内建命令是由shell本身提供的命令,而不是通过外部程序(如位于/bin、/usr/bin等目录下的可执行文件)来执行的命令。这些命令在shell启动的时候就加载到内存中,执行效率相对较高。 常见的内建命令有:cd,pwd,echo。 什么是当前路径 本质…

敏捷开发:Sprint Planning 冲刺计划会议详细介绍和用户故事拆分、开发任务细分

Sprint Planning 冲刺计划会议简介 Sprint Planning (冲刺计划会议),又叫规划会议。此会议通过 Scrum 团队的集体沟通讨论,确定接下来的 Sprint 中要完成的待开发项,把它们组成一个 Sprint Backlog。这些待开发项都是从 Product Backlog 中挑选的。Product Backlog:产品功…

jmeter之性能测试(16.1)

一、性能测试介绍 1、什么叫做性能测试? (1)通过某些工具或手段来检测软件的某些指标是否达到了要求,这就是性能测试 (2)指通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试 2、性能测试的时间? 在功能测试完成后才能进行性能测试…

高级语言程序设计作业 11/25

2024高级语言程序设计:https://edu.cnblogs.com/campus/fzu/2024C 高级语言程序设计课程第五次作业:https://edu.cnblogs.com/campus/fzu/2024C/homework/13311 学号:102400215 姓名:胡加乘14.17 复习题 3 struct month {const char* name; // 月份名const char abbr[4];…

【红队】windows立足点建立技术

本文涵盖了将PowerShell-Empire设置为C2服务器的过程,以及如何生成可用于在目标系统上获得初步立足点的宏Excel文档。 ATT& CK 初始访问包括使用各种进入向量在网络中获得其初始立足点的技术。用于获得立足点的技术包括有针对性的鱼叉式网络钓鱼和利用面向公众的Web服务器…

公钥加密系统与离散对数问题

概念 1 单向函数和陷门信息 单向函数是一种可逆函数,其正向计算容易,但反向计算却非常困难。 安全的公钥加密系统(Public Key Cryptosystem, 简称PKC)基于具有陷门的单向函数。陷门是一种辅助信息,利用它可以轻松计算单向函数的反函数。“陷门”一词来源于物理或机械陷阱的…