浏览器进程与事件循环

进程与线程

进程是一个应用独立运行的基本单位,每个应用都有自己的进程,每个进程都有自己独立的内存空间,有些应用为了避免子应用崩溃的连环反应,也会开启多个进程

线程进程运行的基本单位,每一个进程至少有一个线程,这个线程又被称为主线程,线程也是是程序运行基础单位,可以有多个子线程,且每个线程都是同步有序运行的

如果进程被称为一座房子的话,那么线程就是房子里面干事的人,可以理解为多线程是用来提高效率的(和现实的人一样,不该用人的时候乱用甚至可能还会降低效率)

浏览器进程与线程

每个浏览器都是一个小型的操作系统(只不过一些基础功能基于外部的操作系统),他比我们编写的应用要复杂的多,可以理解为是web应用的操作系统,其有一个进程和多个线程,以保证自己功能的使用

同时为了保证浏览器基础功能,可能会开启多个子进程,打开的页面也会开启自己的进程,打开的页面之间互不影响,当某个进程崩溃时,能够互不影响,甚至可以重新创建新的进程

浏览器有很多进程,其中最重要的就是浏览器进程、网络进程、渲染进程

  • 浏览器进程:主要负责浏览器的页面展示、用户交互(前进后退标签开发者相关等功能),子进程管理等,同时浏览器也会开启多个子线程来处理任务
  • 网络进程:主要用来处理网络事件,加载网络资源,里面会开启多线程来处理网络任务
  • 渲染进程:渲染进程开启后,会启动渲染主线程,会执行 html、css、js等代码,同时,打开其他标签页,也会开启新的进程(后续也许同一个站点不同标签会应用同一个进程)

渲染主线程

讲了进程和线程,实际上渲染主线程才是我们需要关注的重点,毕竟我们的应用被使用在一个新的渲染进程中,通过渲染主线程加载运行我们的程序,我们的程序才得以运行

我们的渲染主线程主要干了啥:

  • 发起请求连接站点,下载html、css资源
  • 解析 html、css
  • 计算属性样式和布局
  • 处理图层和绘制成60帧
  • 执行js代码,执行事件处理函数,执行计时器等
  • ...

可以看到,我们的渲染主线程干了很多很多的事情,同时也发现了不对,为了做了这么多事,前面说了线程相当于人,为什么不开启多线程来提高效率呢,这就是我们后续补充的多线程与多核、同步与异步、串行与并行的事情了,可以结合下面的同步变异步一起理解

js 的异步

  • js是门单线程语言,其执行在我们的渲染主线程,是同步执行的,要是碰到一些非立即执行任务怎么办,例如:网络请求、计时器、点击事件等,不可能一直同步等待吧,这样一直等,会导致整个主线程阻塞掉,什么也干不了,白白浪费性能
  • 为了避免阻塞,当遇到这种非立即执行的任务时,结束他们的执行,交给其他线程处理,继续往后执行,同时,创建一个消息队列,以便于其他线程执行后回调的任务,放入消息队列(对于promise这类微任务,则是执行结束,后续任务直接入队到微任务队列)
  • 主线程的代码执行完毕后,再去消息队列拿任务继续执行
  • 对于这些耗时任务则等到其他子线程执行完了,执行时机到了,则将回调包装成任务放到消息队列

于是引出了后续的消息队列、事件循环

消息队列

开发过程中,会看到很多任务,例如:promise、request、timeout 等任务

我们知道主线程肯定是同步运行的,我们渲染进程中的渲染主线程执行任务时,会直接执行我们同步代码,当碰到 promise、request、timeout 等异步任务时,会将其分类放到不同的消息队列中,等待后续执行,这也就形成了异步(同步执行的代码,形成了异步的效果🤣)

消息队列也有自己的优先级,不过规定的是 微任务队列(promise生成的队列就是微任务) 优先级最高,其他的浏览器自己根据情况决定优先级顺序,不过一般为了不影响用户操作(例如点击事件),会把交互任务排列到第二位

ps:听的最多的就是微任务和宏任务了,实际上只需要知道微任务的优先级比较高就是了,后续任务的执行顺序,还是要看执行和入队时机的,不能无脑看任务优先级,任务优先级只在消息队列中才有效,已经执行的可以不算在内

如下所示:

消息队列与主线程大致交互流程如下所示:

当主线程的任务执行完毕后,会从消息队列,根据队列的优先级,从里面获取一个任务放到主线程执行

主线程执行完毕后在,会再去消息队列中拿取任务(当然执行的过程也会往消息队列入队新的任务)

直到消息队列中的所有任务执行完毕后,线程进入休眠状态,以节约资源

更具体是怎么持续运行的,看后续的事件循环,也就是运行循环

事件循环(运行循环)

事件循环也就是我们最常见的运行循环 runloop 了,其他很多端都要学习到这个知识点,这也是很重要的一个知识点

当线程开启后,执行完毕其中的任务后,如果没有后续任务,则线程会被销毁(系统认为浪费资源会回收),所以,为了保证程序能够持续运行,程序需要开启一个循环,不停的执行任务,来保活线程,这就是运行循环了

ps:就算是其他的平台也差不多,线程是用来执行任务的,当其中没有任务后,就会被销毁

js 复制代码
//运行循环
for (;;) {
    //休眠
    
    //执行任务
}

事件循环是怎么运行的呢?

  • 渲染主线程开始运行时,会启动一个无限循环
  • 每轮循环开始都会从消息队列获取一个新任务(根据优先级),然后执行,执行完毕后,进入下一轮循环
  • 如果新一轮循环开始时,发现消息队列没有任务了,然后阻塞队列,进入休眠状态,等待外部唤醒运行
  • 其他线程(网络请求、计时器、空闲等),当他们时机到了,会往消息队列塞进去新的任务,然后并唤醒事件循环,依次往复

ps:有人问,为什么要死循环呀,没有判断条件么,浏览器需要时怎么退出呀,程序需要我们是退出么,退出循环,不一定要判断条件,一个 throw、return、break 都是可以结束的(问这些的基本功不扎实了)

多线程与多核、同步与异步、串行与并行(补充)

多线程与多核

多核是指 cpu 的多核,从硬件的角度考虑,平时一般 cpu 为了保证顺序执行,一般任务都在一个主核上执行,其他核是为了保证复杂情况能够同时运行时,可以提升性能

线程是在软件和操作系统的层面考虑,一个进程有一到多个线程,一个程序多个线程实际上是开启了异步的过程,多个线程之间是异步的,不关注顺序:

  • 当资源充足时,也就是单核还没跑满,开启多个线程,系统也只会使用一个 cpu核心,只不过会将线程排列任务优先级,依次执行,营造异步现象,听说过cpu时间片么,每个任务执行一个时间片,根据优先级依次往复,保证都能够有所响应
  • 当资源不足时,单核维持不住,此时会开启多个核心,不同线程的任务可能就会分配的不同核心分别执行,反正不关心顺序,只需要执行就行了

对于资源充足不充足,什么时候使用多核,这个就是操作系统和cpu的策略了,我们无法决定

因此,不用盲目追求多线程,看自己的代码场景,滥用弄不好还会降低效率

同步与异步

同步就是顺序执行,有着严格的执行顺序,比如数字 1~9 从小到大只能是1,2,3,4,5,6,7,8,9

异步就是不关注执行顺序,执行过程有着一定的时间差,对于当前执行的任务来说,异步一般表现是延后执行,当然也可能表现实际是紧随其后执行的,都是看情况

同步、异步 往往与 串行、并行 一起讨论

串行、并行、并发

串行表示只能一个一个排队行走,并行表示可以并排行走

串行和并行在生活中的例子也非常多,马路修宽一些,就会减少拥堵现象,这就是并行,小路只能一个一个过,就是串行

有人会经常将并行和并发混淆,实际上并发就是异步,他们不是一个概念,常见多并发,实际上表现就是多线程、多个异步任务,而到了硬件那层,不一定就是并行了,但一般多并发带着写并行的意思,也表示这程序运行效率很棒🤣

一些问题

如何理解js单线程异步问题

js是一门单线程语言,是因为它运行在浏览器的主渲染进程的主线程中,因此只能同步执行,同步实行的过程中,面对消息队列中的一些耗时任务,其无法理解执行完毕,由于同步执行,只能一个一个等待,也就是阻塞了后续任务的执行,对于计算机来说,有任务需要执行,但是还需要等待,那么就是白白浪费了性能

因此,浏览器采用了异步的方式处理,面对这类耗时任务,异步最简单的方式就是开启多线程,于是乎,耗时的网络请求、计时器、用户操作(不可确定时机)等,这类任务则放到其他线程执行,这样我们的js线程就不会被阻塞,可以继续往后执行了,当其他线程的任务执行完毕后,需要执行回调内容,则把回调内容包装成任务,放到任务队列中,等到主线程任务执行完毕后,从消息队列中读取即可,这就是js单线程异步问题了

其中,promise之类的非耗时微任务并不会开启多线程(其只是有一个执行的先后顺序,恰好利用异步和任务队列来实现),其会被加入到微任务队列,等待主线程任务执行完后,下一轮时间循环才陆续执行,引入如果不是必要,无需使用 promise等异步操作,其只会增加整体js的执行时长,白白浪费性能

js事件循环

js使用的是渲染主线程,为了保证线程任务执行完毕后不会被销毁(系统认为浪费资源会回收),需要开启一个运行循环进行线程保活,这就是事件循环(runloop运行循环)

  • 渲染主线程开始运行时,会启动一个无限循环
  • 每轮循环开始都会从消息队列获取一个新任务(根据优先级获取,微任务队列优先级是最高的,其他的看浏览器策略),然后执行,执行完毕后,进入下一轮循环
  • 如果新一轮循环开始时,发现消息队列没有任务了,然后阻塞队列,进入休眠状态,等待外部唤醒运行
  • 其他线程(网络请求、计时器、空闲等),当他们时机到了,会往消息队列塞进去新的任务,然后并唤醒事件循环,依次往复

js计时器能做到精确计时么

不能,有以下几种原因

  • 计算机没有原子钟,无法实现精确计时
  • 操作系统计时函数本身就有少量偏差,js计时器又是使用操作系统的计时器,因此也会出现偏差
  • w3c标准,计时器嵌套5层之后,则最少用于4ms延迟
  • 事件循环中,计时器的回调函数只能在主线程执行,并且优先级较低(至少是在微队列之后执行)

休眠怎么唤醒逻辑主线程的

事件循环是用来保活主线程的,休眠后,循环仍然在,线程没有被销毁,只是阻塞执行了,因为后续没有任务了,休眠也是使用系统的休眠方式,其他线程并没有休眠,当其他线程执行完毕后,会通过线程通信方式,重新激活休眠的线程,这样就唤醒了

就好有两个人(线程)轮流守夜,一个人睡着了,另一个人没睡,轮班的时候,就把睡着的叫醒了,要是两个人都睡着就没办法叫醒了

最后

本文讲了一些浏览器进程、现成等知识,也了并行、串行等知识,并讲了js主进程、主线程、消息队列、事件循环等内容,只是略繁琐,一步一步补充延伸,但就介绍到这里吧

实际上很多知识都是互通的,一通百通,因此了解一些原理什么的是没什么问题的

相关推荐
qq. 28040339847 小时前
CSS层叠顺序
前端·css
喝拿铁写前端8 小时前
SmartField AI:让每个字段都找到归属!
前端·算法
猫猫不是喵喵.8 小时前
vue 路由
前端·javascript·vue.js
烛阴8 小时前
JavaScript Import/Export:告别混乱,拥抱模块化!
前端·javascript
bin91539 小时前
DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加行拖拽排序功能示例12,TableView16_12 拖拽动画示例
前端·javascript·vue.js·ecmascript·deepseek
GISer_Jing9 小时前
[Html]overflow: auto 失效原因,flex 1却未设置min-height &overflow的几个属性以及应用场景
前端·html
程序员黄同学9 小时前
解释 Webpack 中的模块打包机制,如何配置 Webpack 进行项目构建?
前端·webpack·node.js
拉不动的猪9 小时前
vue自定义“权限控制”指令
前端·javascript·vue.js
再学一点就睡9 小时前
浏览器页面渲染机制深度解析:从构建 DOM 到 transform 高效渲染的底层逻辑
前端·css
拉不动的猪9 小时前
刷刷题48 (setState常规问答)
前端·react.js·面试