Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

浏览器的基本工作原理 #57

Open
pfan123 opened this issue Mar 14, 2020 · 0 comments
Open

浏览器的基本工作原理 #57

pfan123 opened this issue Mar 14, 2020 · 0 comments

Comments

@pfan123
Copy link
Owner

pfan123 commented Mar 14, 2020

一个应用程序通常会被划分为几个相互独立又彼此配合的模块, 浏览器也是如此。其实开发一个浏览器,它可以是单进程多线程的应用,也可以是使用 IPC 通信的多进程应用。

不同浏览器采用了不同的架构模式,这里研究以 Chrome 为代表的浏览器,它由多个进程组成,每个进程都有自己核心的职责,它们相互配合完成浏览器的整体功能,每个进程中又包含多个线程,一个进程内的多个线程也会协同工作,配合完成所在进程的职责。

img

进程和线程

进程和线程都是操作系统的概念。

进程(process)

进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,即进程是操作系统进行资源分配和独立运行的最小单元。

当我们启动一个应用,计算机会至少创建一个进程,cpu会为进程分配一部分内存,应用的所有状态都会保存在这块内存中,应用也许还会创建多个线程来辅助工作,这些线程可以共享这部分内存中的数据。如果应用关闭,进程会被终结,操作系统会释放相关内存。

Mac 电脑可以在活动监视器中查看启动的进程数:

img

线程(thread)

  • 进程内部的一个执行单元,是被系统独立调度和分派的基本单位。系统创建好进程后,实际上就启动执行了该进程的主执行线程
  • 进程就像是一个有边界的生产厂间,而线程就像是厂间内的一个个员工,可以自己做自己的事情,也可以相互配合做同一件事情,所以一个进程可以创建多个线程。
  • 线程自己不需要系统重新分配资源,它与同属一个进程的其它线程共享当前进程所拥有的全部资源。 PS: 进程之间是不共享资源和地址空间的,所以不会存在太多的安全问题,而由于多个线程共享着相同的地址空间和资源,所以会存在线程之间有可能会恶意修改或者获取非授权数据等复杂的安全问题。

而现在通用叫法单线程与多线程,都是指在一个进程内的单和多。

如果对进程及线程的理解还存在疑惑,可以参考下述文章 www.ruanyifeng.com/blog/2013/0…

关于单核处理器、多核处理器、多处理器是怎么处理进程和线程的,可以参考下述文章 blog.csdn.net/alinshen/ar… jsonliangyoujun.iteye.com/blog/235827…

Chrome 浏览器的多进程架构

img

每打开一个tab页,就相当于于创建了一个独立的浏览器进程,这一点从上面的图中可以看出,但是也不是绝对的,它也有自己的优化机制,有的进程可能会被合并。

1、Chrome 的主要进程及其职责

  • Browser Process 浏览器的主进程(负责协调、主控)
    (1)负责包括地址栏,书签栏,前进后退按钮等部分的工作
    (2)负责处理浏览器的一些不可见的底层操作,比如网络请求和文件访问
    (3)负责各个页面的管理,创建和销毁其他进程
  • Renderer Process 负责一个 tab 内关于网页呈现的所有事情,页面渲染,脚本执行,事件处理等
  • Plugin Process 负责控制一个网页用到的所有插件,如 flash 每种类型的插件对应一个进程,仅当使用该插件时才创建
  • GPU Process 负责处理 GPU 相关的任务

img

Chrome 还为我们提供了「任务管理器」,供我们方便的查看当前浏览器中运行的所有进程及每个进程占用的系统资源,双击还可以查看更多类别信息。

通过「页面右上角的三个点点点 — 更多工具 — 任务管理器」即可打开相关面板。

img

2、Chrome 多进程架构的优缺点

优点:

  • 某一渲染进程出问题不会影响其他进程

  • 更为安全,在系统层面上限定了不同进程的权限

缺点:

  • 由于不同进程间的内存不共享,不同进程的内存常常需要包含相同的内容。为了节省内存,Chrome 限制了最多的进程数,最大进程数量由设备的内存和 CPU 能力决定,当达到这一限制时,新打开的 Tab 会共用之前同一个站点的渲染进程。

Chrome 把浏览器不同程序的功能看做服务,这些服务可以方便的分割为不同的进程或者合并为一个进程。

以 Broswer Process 为例,如果 Chrome 运行在强大的硬件上,它会分割不同的服务到不同的进程,这样 Chrome 整体的运行会更加稳定,但是如果 Chrome 运行在资源贫瘠的设备上,这些服务又会合并到同一个进程中运行,这样可以节省内存。

浏览器的多线程

对我们前端来讲,最重要的是 Renderer Process 下的多线程,就是我们常说的浏览器内核。

Chrome 浏览器为每个 tab 页面单独启用进程,因此每个 tab 网页都有由其独立的渲染引擎实例

浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:

1、GUI 渲染线程

负责渲染浏览器界面HTML元素,当界面需要重绘 (Repaint) 或由于某种操作引发回流 (reflow) 时,该线程就会执行。在 Javascript 引擎运行脚本期间,GUI 渲染线程都是处于挂起状态的,GUI渲染线程与 JS 引擎线程是互斥的。

2、JavaScript 引擎线程

Javascript 引擎 ,也可以称为 JS 内核,主要负责处理 Javascript 脚本程序,例如 V8 引擎。Javascript引擎线程,负责解析 Javascript 脚本,运行代码。 它是基于事件驱动单线程执行的,JavaScript 引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个 JavaScript 线程在运行 JavaScript 程序。

ps: GUI 渲染线程与 Javascript 引擎线程是互斥的,所以如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。

3、定时触发器线程

  • 定时器 setInterval 与 setTimeout 所在线程
  • 浏览器定时计数器并不是由 JavaScript 引擎计数的,因为 JavaScript 引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确, 因此通过单独线程来计时并触发定时是更为合理的方案。

4、事件触发线程

当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待 JavaScript 引擎的处理。这些事件可以是当前执行的代码块如定时任务、也可来自浏览器内核的其他线程如鼠标点击、AJAX 异步请求等,但由于 JavaScript 的单线程关系所有这些事件都得排队等待 JavaScript 引擎处理;

5、异步http请求线程

XMLHttpRequest 在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript 引擎的处理队列中等待处理。

JavaScript 单线程

这是由 Javascript 这门脚本语言的用途决定的。

作为浏览器脚本语言,JavaScript 主要用于处理页面中用户交互,以及操作 DOM 树、CSS 样式树(当然也包括服务器逻辑的交互处理)。

如果 JavaScript 是多线程的方式来操作这些 UI DOM,则可能出现UI操作的冲突。

这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定 JavaScript 同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

当然我们可以通过锁来解决上面的问题。但为了避免因为引入了锁而带来更大的复杂性,从一诞生,JavaScript 就选择了单线程执行,这已经成了这门语言的核心特征。

这也解释了为什么 GUI 线程和 JavaScript 引擎线程是互斥的。

当 JavaScript 引擎执行时 GUI 线程会被挂起,GUI 更新则会被保存在一个队列中等到 JavaScript 引擎线程空闲时立即被执行。

为了多核 CPU 的计算能力,HTML5提出 Web Worker 标准,允许 JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM。所以,这个新标准并没有改变 JavaScript 单线程的本质。

JavaScript的单线程产生的问题与处理方案

页面卡顿的真正原因

由于 JavaScript 是可操纵 DOM 的,如果在修改这些元素属性同时渲染界面(即 JavaScript 线程和 UI 线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。为了防止渲染出现不可预期的结果,浏览器设置 UI 渲染线程与 JavaScript 引擎线程为互斥的关系,当 JavaScript 引擎线程执行时 UI 渲染线程会被挂起,UI 更新会被保存在一个队列中等到 JavaScript 引擎线程空闲时立即被执行

于是,我们便明白了:假设一个 JavaScript 代码执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染出现“加载阻塞”的现象。当然,针对 DOM 的大量操作也会造成页面出现卡顿现象,毕竟我们经常说:DOM 天生就很慢。

img

所以,当你需要考虑性能优化时就可以从如上的原因出发,大致有以下几个努力的方面:

  • 减少 JavaScript 加载对 DOM 渲染的影响(将 JavaScript 代码的加载逻辑放在 HTML 文件的尾部,减少对渲染引擎呈现工作的影响);
  • 避免重排,减少重绘(避免白屏,或者交互过程中的卡顿);
  • 减少 DOM 的层级(可以减少渲染引擎工作过程中的计算量);
  • 使用 requestAnimationFrame 来实现视觉变化(一般来说我们会使用 setTimeout 或 setInterval 来执行动画之类的视觉变化,但这种做法的问题是,回调将在帧中的某个时点运行,可能刚好在末尾,而这可能经常会使我们丢失帧,导致卡顿);

有关优化的方面可以查看《 优化 JavaScript 执行》一文了解更多信息。

任务队列 Event Loop

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

如果排队是因为计算量大,CPU 忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。

JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。

于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

img

Other Resource

图解浏览器的基本工作原理

聊聊 JavaScript 与浏览器的那些事 - 引擎与线程

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant