浏览器进程
并行处理
并行处理 Parallel Processing 是计算机系统中能同时执行两个或多个处理的一种计算方法。并行处理可同时工作于同一程序的不同方面
进程和线程
进程是资源(CPU、内存等)分配的基本单位,具有一定功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位
。
线程是进程的一个实体,是独立运行和独立调度的基本单位(CPU 上真正运行的是线程)。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
一个进程就是一个程序的运行实例。就是启动一个程序时,操作系统会为该程序创建一块内存,这块内存用来存放代码、运行数据和一个执行任务的主线程,上面的这样一个运行环境统叫进程。
关系
进程中的任意一线程执行出错,都会导致整个进程的崩溃。
线程之间共享进程中的数据。
当一个进程关闭之后,操作系统会回收进程所占用的内存。
进程之间的内容相互隔离。
也不是完全隔离,进程之间在特定情况下还是可以进行通信(IPC 机制) IPC(Inter-Process Communication,进程间通信)。进程间通信是指两个进程的数据之间产生交互。
单进程浏览器
单进程浏览器是指浏览器所有功能模块(网络、插件、JavaScript 运行环境、渲染引擎和页面等)都是运行在同一个进程里。谷歌浏览器发布前浏览器多是单进程的。
单进程的问题:
不稳定:插件和渲染引擎会导致整个进程崩溃
不流畅:JavaScript 执行环境一旦出现大量计算或者循环就会导致整个进程运行变慢或卡顿,内存泄漏也是一大原因。
不安全:第三方插件可以编写相关调用操作系统底层的恶意代码来获取系统信息;页面脚本可以通过浏览器漏洞获取系统权限造成安全问题。
多进程浏览器
多进程浏览器泛指谷歌浏览器等现代浏览器。
早期多进程架构
2008 年谷歌发布时的进程架构:
目前多进程架构
最新的谷歌浏览器包括了 5 个进程:
1、浏览器进程
主要负责
界面显示
、用户交互、子进程管理
,同时提供存储等功能。
2、渲染进程
核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
渲染进程启动后,会开启一个
渲染主线程
,主线程负责执行 HTML、CSS、JS 代码。
3、GPU进程
Chrome 刚开始发布的时候是没有 GPU 进程的。而 GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程。
4、网络进程
主要负责
页面的网络资源加载
,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
5、插件进程
主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响
打开一个网页,至少有 4 个进程,即至少需要 1 个网络进程、1 个浏览器进程、1 个 GPU 进程和一个渲染进程;如果打开的页面有运行插件,还需要加上 1 个插件进程。
页面渲染过程及进程之间的交互
1、导航栏输入一个内容,浏览器进程
里的UI线程
会先进行解析判断是 URL 还是搜索关键词,如果是 URL,会通过 IPC 通道将信息传送给网络进程
2、网络进程
拿到 URL 后,以数据包的形式通过TCP/IP
协议栈来获取响应的数据,此时网络进程
会将拿到的内容与 URL 一并交给 SafeBrowsing
做安全检查,检查内容是否与某个已知的网站相匹配以及这个 IP 是否在黑名单里,如果有安全风险会先展示警告页面询问是否继续访问。
3、返回的数据准备完毕并且无风险,网络进程
会将数据返回给UI线程
,UI线程
就会找渲染进程
,进入渲染阶段。
4、渲染进程
的任务是把 html、css、JavaScript 等资源进行计算,然后转换为显示器的像素点,最终合成帧
,返回给浏览器进程
。
具体渲染过程见下一篇文章。
5、浏览器进程
将渲染进程
里的合成帧
发送给GPU进程
,GPU进程
将其进行渲染
并展示在屏幕上,至此用户就可以看到页面了。
多进程的缺点
1、更高的资源占用
2、更复杂的体系架构
多进程
浏览器进程(主进程)
与其他进程一起协作,实现浏览器的功能;
负责浏览器界面显示,与用户交互。如前进,后退等;
负责各个页面的管理,创建和销毁其他进程;
将 Renderer 进程得到的内存中的 Bitmap,绘制到用户界面上;
网络资源的管理,下载等。
包含的线程:
UI 线程:绘制浏览器顶部按钮和导航栏输入框等组件;
网络线程:管理网络请求;
存储线程:控制文件读写;
GPU 进程:最多一个
- 最多一个,用于合成图层、3D 图形绘制等
渲染进程(Renderer 进程,也叫浏览器内核)
每个 tab 页会单独占用一个渲染进程;
tab 页内的
<iframe>
也会占用独立的渲染进程;用于页面渲染、Js 执行、事件循环;
渲染进程是多线程的,主要有:
GUI 渲染线程
JS 引擎线程;
事件触发线程: 管理任务队列;
定时触发器线程;
异步 http 请求线程
包含的线程:
1、GUI 渲染线程(主线程)
数目:1 个
主要职能:
1、初始渲染:解析 HTML,解析 CSS,构建 DOM 树,CSSOM 树,整合 Render 树,进行布局和绘制;
2、页面发生变化时,执行重绘和重排
3、GUI 渲染线程与 JS 引擎线程是互斥的(不可同时运行)。当 JS 引擎执行时 GUI 线程就会被挂起(相当于被冻结了),GUI 更新会被保存在一个队列中,等到 JS 引擎线程空闲时,再立即取出执行
2、JS 引擎线程
数目:1 个
主要职能:
1、运行 JS 引擎(V8):解析 JS 脚本,运行 JS 代码;
2、JS 引擎中有一个任务队列,它一直等待任务队列中任务的到来,一旦任务到来立即按序加以处理
3、与 GUI 渲染线程互斥。(因此 Js 执行时间如果过长,会导致页面渲染卡顿)
3、事件触发线程
数目:1 个
主要职能:控制事件循环
1、当 JS 执行遇到宏任务、微任务时,将其加入到事件触发线程的对应队列中;
2、事件触发线程会根据任务的执行时机(比如 setTimeout 宏任务约定的定时时间已到),将任务各自队列中取出,放入 JS 引擎任务队列中等待执行
3、JS 引擎会在自己空闲的时候,从队列中依次取出任务执行
4、定时触发器线程
主要职能:
1、用于 setTimeout / setInterval 等的计时;
2、时间到,则通知事件触发线程,将定时器对应的任务放入 Js 引擎的任务队列
3、HTML 标准中要求,低于 4ms 的定时,时间间隔都算作 4ms(也就是定时器最低时间间隔为 4ms)
5、异步 http 请求线程
主要职能:
1、处理异步 http 请求;
2、请求结果返回,通知事件触发线程,将回调任务放入 JS 引擎任务队列;
第三方插件进程
每个第三方插件可能对应一个进程;
使用程序进程:储存进程、网络进程、音频进程等
面向服务的架构
为了解决多进程架构的问题,在 2016 年,Chrome 官方团队使用“面向服务的架构”(Services Oriented Architecture,简称SOA)
的思想设计了新的 Chrome 架构。也就是说 Chrome 整体架构会朝向现代操作系统所采用的“面向服务的架构” 方向发展,原来的各种模块会被重构成独立的服务(Service)
,每个服务(Service)都可以在独立的进程中运行,访问服务(Service)必须使用定义好的接口,通过 IPC 来通信,从而构建一个更内聚、松耦合、易于维护和扩展的系统
,更好实现 Chrome 简单、稳定、高速、安全的目标。
Chrome 最终要把 UI、数据库、文件、设备、网络等模块重构为基础服务,类似操作系统底层服务
扩展——协程
协程 Coroutines 是一种比线程更加轻量级的存在。
协程完全由程序所控制(在用户态执行),带来的好处是性能大幅度的提升。
一个操作系统中可以有多个进程;一个进程可以有多个线程;一个线程可以有多个协程。
协程是一个特殊的函数,这个函数可以在某个地方挂起,并且可以重新在挂起处继续运行。
一个线程内的多个协程的运行是串行的,这点和多进程(多线程)在多核 CPU 上执行是不同的。
多进程(多线程)在多核 CPU 上是可以并行的。当线程内的某一个协程运行时,其他协程必须挂起。
JavaScript 协程的发展
同步代码
异步 JavaScript:callback hell(回调地狱)
ES6 引入 promise/a+,生成器 Generators(语法
function foo(){}*
,可以赋予函数执行暂停/保存上下文/恢复执行状态的功能),新关键词 yield 使生成器函数暂停。ES7 引入 async 函数/await 语法糖,async 可以声明一个异步函数(将 Generator 函数和自动执行器包装在一个函数里),此函数需要返回一个 Promise 对象,await 可以等待一个 Promise 对象 resolve,并拿到结果
Promise 中也利用了回调函数,在 then 和 catch 方法中都传入了一个回调函数,分别在 Promise 被满足和拒绝时执行,这样就能让它能够被链接起来完成一系列任务。总之是把层层嵌套的 callback 变成.then().then()...,从而使代码编写和阅读更直观
进程、线程、协程的区别
1、进程是资源分配的基本单位(计算、存储、I/O三大资源);进程间是完全独立的,互不干扰,交换数据需要管道、消息队列之类的;
2、线程共享同一个进程的资源(如内存空间中的代码段,数据段,堆,文件描述符等),交换数据更方便。栈是每个线程特有的,因为线程是程序执行的最小单位,需要记录自己的局部变量等。
3、线程切换会进行线程上下文切换。线程在运行时,实际上是在执行代码,而执行过程中需要存储一些中间数据,也可能会执行一些I/O操作。如果过程中被中断,需要保存一些信息,以便下次恢复运行。
这些信息包括下一个要执行的代码,这个存储在程序计数器中;然后是一些中间数据如局部变量,会存储在线程栈中,为了加速计算,中间数据中对当前指令执行至关重要的部分会存储在寄存器中。所以,程序计数器、寄存器、线程栈指针也需要保存。
4、协程相比于线程,创建和切换的开销极小。这是因为他并非操作系统层面的东西,就不设计内核调度,一般是由编程语言来实现(如python的asyncio标准库),它属于用户态的东西。
线程的执行时机由操作系统调度,程序员无法控制,这正是多线程容易出现资源覆盖的主要原因
而协程的执行时机由程序员自身控制,不受操作系统调度影响,因此可以完全避免这类问题
同时,同一个线程内的多个协程共享同一个线程的CPU时间片资源,它们在CPU上的执行是有先后顺序的,不能并行执行,而线程是可以并行执行的
协程coroutine,其实是一种特殊的子程序subroutine,比如普通函数。普通函数一旦执行就会从头到尾运行,然后返回结果,中间不会暂停,而协程则可以在执行到一半时暂停,利用这一特性,可以在遇到I/O这类不消耗CPU资源的操作时,将其挂起,继续执行其他计算任务,充分利用CPU资源,等I/O操作结果返回时,在恢复执行。
协程的目的就是在一个线程内并发执行多个任务
参考资料
https://www.cnblogs.com/wx980416/p/16380357.html
https://juejin.cn/post/7178033357601636409?searchId=20240711185221F02B40947082C19EAAA2