上一篇👉: 浏览器安全
文章目录
- 进程与线程
- 1.进程与线程概念
- 2.进程和线程的区别
- 进程的特性
- 线程的特性
- 通信与同步
- 3.浏览器渲染进程中的线程
- 4.什么是僵尸进程和孤儿进程
- 5.死锁
- 资源类型
- 产生死锁的原因
- 必要条件
- 解决策略
- 6.进程通信方式
- 7. 如何实现浏览器内多个标签页之间的通信?
- 8. 对Service Worker的理解
进程与线程
1.进程与线程概念
进程与线程是现代操作系统中处理任务和并发执行的基础概念,它们分别代表了不同层级的资源管理和执行单元。
- 进程(Process):操作系统中程序执行和资源管理的独立实体。每个进程都拥有独立的地址空间,这意味着它有自己的一套内存、数据栈、打开文件以及安全上下文等资源。进程是系统进行资源分配(如内存和CPU时间)的最小单位。当一个程序开始执行时,操作系统为其创建一个进程,这个进程代表了该程序的运行实例。进程之间相互隔离,以防止一个进程中的错误影响到其他进程,这种设计增强了系统的稳定性(健壮性)和安全性。然而,进程间的通信和数据共享相比线程较为复杂,通常需要借助于进程间通信(IPC)机制来实现。
- 线程(Thread):是进程内的执行单元,是CPU调度的最小单位。线程共享所属进程的内存空间和资源,线程间的通信和同步相对简单高效,因为它们可以访问同一地址空间的数据。线程的引入使得程序能够并行执行多个任务,提高了执行效率和响应速度。然而,一个线程的错误可能导致整个进程崩溃,因为所有线程共享同一地址空间。
进程与线程之间的关系 主要有四点:
- 任一线程的执行错误会导致所在进程的崩溃。
- 同一进程内的线程共享进程资源,包括内存和文件句柄。
- 进程终止时,操作系统会回收其占用的所有资源,即便线程导致内存泄漏,也不会长期占用系统资源。
- 进程之间在内存和资源上是相互隔离的,提供了安全边界,必要时通过IPC机制实现数据交流。
以 Chrome浏览器 的架构为例,它展示了多进程模型的典型应用:
- 浏览器主进程 负责界面展示、用户交互管理以及协调其他子进程。
- 渲染进程 负责将网页内容(HTML、CSS、JavaScript)转换为用户可交互的形式,并在沙盒环境中运行以提高安全性。
- GPU进程 专用于图形处理,提升图形渲染和计算能力。
- 网络进程 负责处理页面资源的加载,以减轻浏览器主进程负担并增强性能。
- 插件进程 为每个插件单独分配进程,以防插件崩溃影响浏览器稳定性。
flowchart TBsubgraph "浏览器主进程(Browser Process)"BP[Browser<br>Process]endsubgraph "渲染进程(Renderer Processes)"RP1(Renderer Process)<br>(Tab 1)RP2(Renderer Process)<br>(Tab 2)RP3(Renderer Process)<br>(Tab 3)endsubgraph "GPU进程(GPU Process)"GPUGP[GPU Process]endsubgraph "网络进程(Network Process)"NP[Network Process]endsubgraph "插件进程(Plugin Processes)"PP1[Plugin Process]<br>(Plugin 1)PP2[Plugin Process]<br>(Plugin 2)endclassDef defaultClass fill:#F9F9F9,stroke:#333,stroke-width:2px;BP -->|Manages UI, Disk I/O, Network requests| NPBP -->|Handles top-level UI, creates other processes| RP1BP -->|Handles top-level UI, creates other processes| RP2BP -->|Handles top-level UI, creates other processes| RP3BP -->|Manages GPU tasks| GPUGPBP -->|Spawns for each plugin instance| PP1BP -->|Spawns for each plugin instance| PP2RP1 -->|Render Web Content| NPRP2 -->|Render Web Content| NPRP3 -->|Render Web Content| NPclass RP1,RP2,RP3,PP1,PP2,GPUGP,NP defaultClass
flowchart流程图代码无法渲染 看下面👇
+-----------------------+ +----------------------+ +-------------------+
| 浏览器主进程 |◄──────| 网络进程 |◄─────┐ | GPU进程 |
| (Browser Process) | | (Network Process) | | (GPU Process) |
| | | | | |
| - 管理UI界面 | | - 处理网络请求 | | - 图形处理与加速 |
| - 协调各组件 | | | | |
+-----------------------+ +----------------------+ +-------------------+| | || 创建、管理 | |👇 | |
+------------------+ +------------------+ +------------------+
| 渲染进程 1 | | 渲染进程 N | | 插件进程 1 |
| (Renderer Process)| | (Renderer Process)| | (Plugin Process) |
| - 渲染Tab页面 | | - 渲染其他Tab页面 | | - 运行插件代码 |
| - 执行JavaScript | | - 执行JavaScript | | |
+------------------+ +------------------+ +------------------+
这种多进程架构虽提高了浏览器的稳定性和安全性,但同时也带来了资源消耗增加和系统架构复杂度提升的问题。例如,每个进程包含的公共基础结构(如JavaScript运行环境)副本会占用更多内存,而复杂的进程间通信和协调机制则要求更高的设计与维护成本。
2.进程和线程的区别
特性 | 进程 | 线程 |
---|---|---|
概念界定 | 进程可以视为操作系统中的独立应用程序实体,承载完整的执行环境。 | 线程是进程内部的执行路径,是CPU执行调度的基本单位。 |
资源分配与管理 | 进程作为资源分配的最小单位,拥有独立的内存空间、I/O资源等。 | 线程不直接分配系统资源,共享所属进程的资源,降低了资源开销。 |
独立运行能力 | 进程间相互独立,一个进程崩溃不会直接影响其他进程。 | 线程间共享地址空间,一个线程崩溃可能导致整个进程崩溃。 |
CPU调度与切换 | 进程间的切换涉及整个地址空间和状态保存,开销较大。 | 线程切换仅需保存和恢复少量寄存器内容,开销小,效率高。 |
通信机制 | 进程间通信(IPC)通常需要专门的机制,如管道、消息队列等。 | 线程间可以直接读写同一内存空间的数据,通信简便但需同步控制。 |
系统开销与创建 | 创建或撤销进程,系统需分配回收资源,开销较大。 | 线程的创建和销毁相对快速,系统开销小。 |
应用场景与优势 | 适用于构建独立、隔离的执行环境,适合大型、复杂应用。 | 适合于需要高并发处理和资源共享的场景,提升程序执行效率。 |
进程的特性
- 独立性与应用程序视图:进程可以被视为操作系统中的一个独立的执行环境,它代表了一个正在运行的应用程序实例。每个进程都有自己的内存空间,包括代码段、数据段、堆栈以及程序计数器等,这使得进程之间相互独立,一个进程的崩溃通常不会直接影响到其他进程的稳定性。
- 资源分配的最小单位:在操作系统层面,进程是资源分配的基本单元。操作系统为每个进程分配独立的资源,如内存、文件句柄、I/O设备等。这种资源的隔离性是通过为每个进程提供独立的虚拟地址空间来实现的,确保了进程间的资源安全。
线程的特性
-
轻量级执行单元:线程是进程内部的更小的执行序列,是CPU调度的基本单位。一个进程可以包含多个线程,这些线程共享所属进程的地址空间,包括代码、数据以及打开的文件等,这使得线程之间的通信和数据共享变得更加直接和高效。
-
调度与切换开销:相比于进程切换,线程之间的切换开销要小得多。线程切换时,只需要保存和恢复少量的上下文信息(主要是寄存器状态),而进程切换则需要保存和恢复整个进程上下文,包括内存映射、栈信息等,因此开销更大。
通信与同步
-
线程间通信:线程间可以直接访问共享的内存空间进行通信,这种方式快速而直接,但同时也可能引入竞态条件和数据同步问题,需要使用锁、信号量等同步机制来解决。
-
进程间通信:由于进程间的地址空间相互独立,它们之间不能直接访问对方的内存,因此进程间的通信需要借助于进程间通信(IPC)机制,如管道、消息队列、共享内存、套接字等,这相比线程间通信更为复杂和耗时。
总而言之,进程作为资源分配的最小单位,提供了独立的执行环境,适合于实现大型应用程序的模块化和隔离性。而线程作为CPU调度的最小单位,强调轻量级和高效的并发执行,尤其适用于需要大量并发处理的场景。两者各有优势,且在实际应用中常常结合使用,以实现复杂多变的并发需求和资源管理。
3.浏览器渲染进程中的线程
线程名称 | 职责 | 特性与规则 |
---|---|---|
GUI渲染线程 | 解析HTML和CSS,构建DOM树、CSSOM树、渲染树;执行页面布局与绘制像素。 | 与JS引擎互斥执行。 |
JavaScript引擎线程 | 执行JavaScript代码;顺序执行,管理任务队列。 | 阻塞渲染直至JS代码执行完成。 |
事件触发线程 | 监听并分发事件(例如点击事件、定时器事件)至JS引擎的任务队列。 | 支持事件的异步处理,提高响应性。 |
定时器触发线程 | 负责setTimeout与setInterval的计时与调度。 | 计时结束时,将回调函数加入JS引擎任务队列;最小延迟接近4ms。 |
异步HTTP请求线程(XHR) | 处理异步HTTP请求(Ajax),独立于主线程运行。 | 允许网络请求与页面渲染并行,通过回调通知JS引擎请求状态变更。 |
这些线程共同协作,实现了现代网页复杂且高效的交互体验,确保了用户界面的流畅更新、及时的事件响应以及后台数据的异步获取,而这一切都在单个主线程(负责JavaScript执行)与其他辅助线程的协同下高效进行。
- GUI渲染线程
职责: 负责页面的布局和绘制,包括解析HTML和CSS、构建DOM树、CSSOM树、渲染树以及最终的像素绘制。
注意: 与JavaScript引擎线程互斥执行,意味着当JS执行时,渲染会被暂停,直到JS执行完成。 - JavaScript引擎线程
职责: 执行JavaScript代码,处理脚本逻辑。
特性: 单线程执行,确保执行顺序,避免并发问题。处理来自任务队列的任务。
影响: 长时间运行的JS脚本会阻塞页面渲染。 - 事件触发线程
职责: 监控各类事件(如鼠标点击、定时器到期),将这些事件放入任务队列等待JS引擎执行。
作用: 解耦事件监听与实际处理,保证事件响应的异步性。 - 定时器触发线程(实际上应归类于事件触发线程管理)
特指: setInterval与setTimeout相关的任务。
机制: 计时完毕后,事件加入到事件队列,等待JS引擎执行,并非绝对准时。
规则: 最小时间间隔为4ms,以防止计时器过度消耗资源。 - 异步HTTP请求线程(XMLHttpRequest)
职责: 处理Ajax请求,独立于主线程,避免网络请求阻塞页面。
流程: 发起请求后,线程监控状态变更,状态变更时通过事件回调机制通知JS引擎处理响应。
流程图描述:
下面用一个简化的流程图来详细表示浏览器多线程间的基本交互流程:👇
4.什么是僵尸进程和孤儿进程
概念 | 描述 | 处理方式 |
---|---|---|
孤儿进程 | 当父进程终止,其子进程若仍在运行,则这些子进程成为孤儿进程。孤儿进程将被init进程(PID为1)接管并管理至结束。 | 由init进程自动接管,负责资源回收和进程终结。 |
僵尸进程 | 子进程提前终止,但父进程未通过wait() 或waitpid() 系统调用来回收其状态,此时子进程残留的PCB(进程控制块)成为僵尸。 | 需父进程回收资源,否则将持续占用系统资源,直至父进程处理或系统重启。 |
5.死锁
死锁是操作系统中多进程并发执行时,因相互争夺资源而造成的一种僵局状态,若无外力干预,这些进程都将无法继续推进。
资源类型
- 可剥夺资源:如CPU时间片和内存,操作系统可以强行从一个进程转移到另**一个进程。
- 不可剥夺资源**:如打印机,一旦分配给某个进程,除非该进程释放,否则不能被其他进程使用。
产生死锁的原因
- 资源竞争:特别是不可剥夺资源的竞争,如单一打印机被一个进程占用后,其他需要打印机的进程必须等待。
- 通信顺序不当:进程间通信的顺序问题,如信号、消息的不当处理,也能导致死锁。
- 进程推进顺序非法:如进程P1持有资源R1,等待R2;进程P2持有R2,等待R1,形成互相等待的循环。
必要条件
死锁的产生需满足以下四个条件:
- 互斥使用:资源在同一时间只能被一个进程独占。
- 保持与等待:进程已持有至少一个资源,同时又申请新的资源,被拒后不释放已占资源。
- 不可抢占:进程已获得的资源在使用完毕前,不能被强制夺走。
- 循环等待:存在一种进程和资源的循环等待链。
解决策略
- 预防死锁的方法: 一次性分配所有资源:要求进程启动前声明所有需求资源**,成功则执行,失败则等待,避免后续请求。(破坏请求条件)
- 限制资源申请:若进程无法获取某资源,则不分配任何资源,避免保持与等待状态。(破坏保持条件)
- 资源可剥夺性增强:当进程无法获取额外资源时,可主动释放已占资源,以供其他进程使用。(破坏不可剥夺条件)
- 资源有序分配策略:为资源分配全局编号,进程按编号顺序请求资源,释放则反序进行,避免环路。(破坏环路等待条件)
6.进程通信方式
进程间通信(Inter-Process Communication, IPC)是多进程系统中不可或缺的一部分,它允许不同的进程之间交换信息和数据。以下是几种常见的进程间通信方式的整理描述:
(1)管道(Pipe)通信
- 简介:管道是一种半双工的通信方式,分为匿名管道和命名管道。匿名管道主要用于具有亲缘关系的进程间通信,而命名管道则打破了这一限制,允许任意进程通过共享的管道名进行通信。
- 特点:单向或半双工数据流动,依赖于文件系统(命名管道),与创建它的进程生命周期无关(命名管道),并且在管道内部实现了同步机制。
(2)消息队列(Message Queue)通信
-
简介:消息队列是一个在内核中维护的消息链表,允许进程向队列中插入消息或从中读取消息。
-
特点:提供了一种可靠的消息传递机制,允许多对多通信,消息可以包含不同数据类型,但受到单个消息大小的限制。消息队列的使用避免了管道的同步和阻塞问题,但频繁读写可能导致性能开销。
(3)信号量(Semaphore)通信
- 简介:信号量是一种计数器,用于解决进程间的同步与互斥问题。当资源可用时,信号量的值增加;当资源被占用时,信号量值减少。
- 作用:通过控制多个进程对共享资源的访问,避免冲突,实现同步和互斥控制。
(4)信号(Signal)通信
- 简介:信号是一种软中断,用于通知进程有某种事件发生,如错误条件或用户请求。
- 特点:作为一种简单的异步通信方式,信号处理可以是默认操作(如终止进程)、忽略或自定义处理。
(5)共享内存(Shared Memory)通信
-
简介:共享内存是将一部分物理内存映射到多个进程的地址空间,从而实现直接读写通信。
-
优势:提供了最快的数据交换方式,适用于大量数据交换的场景,但需要配合信号量等同步机制来避免并发访问冲突。
(6)套接字(Socket)通信
- 简介:套接字不仅限于同一台机器上的进程通信,还能跨越网络,实现不同主机间进程的通信。
- 应用:广泛应用于互联网通信,如HTTP、FTP协议,以及分布式系统中的进程间通信,提供了全双工的通信能力。
通信方式 | 特点 | 说明 |
---|---|---|
管道通信 | - 只能单向通信 - 仅限血缘关系进程 - 文件系统依赖 - 随进程生命周期 - 字节流服务 - 内置同步 | 基于内核的缓冲区,适合父子进程间简单数据交换。 |
消息队列通信 | - 多对多通信 - 消息类型区分 - 数据块长度限制 - 避免管道同步问题 | 提供消息存储与转发,适用于更复杂的进程间数据传输,但需注意数据大小限制。 |
信号量通信 | - 解决共享内存竞争 - 互斥与同步控制 - 计数器机制 | 用于管理多个进程对共享资源的访问权限,保证数据一致性与访问控制。 |
信号通信 | - 古老的通信方式 - 系统事件通知 - 用户自定义处理机制 | 系统级通知机制,用于告知进程系统事件,如错误或用户请求,支持有限的信号处理逻辑。 |
共享内存通信 | - 高速数据交换 - 多进程共享访问 - 需同步机制(如信号量) | 直接映射内存区域,实现高速数据共享,但需额外同步机制避免数据冲突。 |
套接字通信 | - 跨网络通信 - 全双工 - 适用于不同主机间进程通信 | 不受地域限制,支持网络中任意两台主机上的进程通信,是实现互联网服务的基础。 |
7. 如何实现浏览器内多个标签页之间的通信?
实现浏览器内多个标签页之间的通信,确实需要借助某种形式的中介或共享资源来协调信息的传递。下面几种方法是实践中常用的策略:
1. 使用WebSocket
原理: WebSocket提供了一种在客户端和服务器之间建立长连接的机制,允许全双工通信。通过WebSocket,浏览器和服务器可以实时推送数据,因此,可以利用WebSocket服务器作为中介,让不同标签页通过它交换信息。
步骤:在每个标签页中建立与WebSocket服务器的连接。
当一个标签页需要发送消息给其他标签页时,将消息发送到WebSocket服务器。
服务器接收到消息后,广播给所有连接的客户端(即其他标签页)。
接收方标签页通过WebSocket连接的事件监听器接收并处理消息。
2. 使用SharedWorker
原理: SharedWorker是一种特殊的Web Worker,能够在多个浏览器上下文(如标签页)之间共享,因此可以作为一个中心节点来促进通信。
步骤:
- 创建一个SharedWorker脚本,并在各个标签页中实例化这个SharedWorker。
- 利用SharedWorker提供的port对象发送和监听消息。
- 当一个标签页通过worker.port.postMessage发送消息时,其他关联的标签页可以通过监听onmessage事件来接收这些消息。
3. 利用localStorage和Storage事件
原理: localStorage是一种持久化的客户端存储机制,而Storage事件会在同一个域名下任何文档的storage发生变化时触发。这意味着一个标签页对localStorage的修改可以被其他标签页监听到。
步骤:
- 各标签页监听window的storage事件。
- 当一个标签页需要发送消息时,修改localStorage的一个特定键值对。
- 其他标签页通过storage事件监听器捕捉到变化,并作出相应处理。
4. 使用postMessage和window.open或window.opener
原理: postMessage方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨窗口或跨域通信。如果两个标签页之间有直接的引用关系(比如一个页面通过window.open打开了另一个页面),则可以直接使用postMessage。
步骤:
- 打开新标签页时,使用window.open并保存新窗口的引用。
- 发送消息时,通过引用调用windowRef.postMessage(data, targetOrigin)。
- 在接收端,设置window.addEventListener(‘message’, function(event) { …
})来监听消息。
8. 对Service Worker的理解
Service Worker 是一种在浏览器后台独立于主线程运行的脚本,它设计的初衷是为了实现离线体验、背景同步、推送通知等功能,极大地增强了Web应用的能力。以下是关于Service Worker的一些核心理解点:
- 运行环境:Service Worker运行在浏览器后台,与网页不在同一个线程上,因此它有能力在页面关闭或浏览器最小化时持续运行,执行一些非用户界面相关的任务。
- HTTPS要求:由于Service Worker能够拦截网络请求、控制页面资源的加载过程,为了保证安全性,其只能在HTTPS环境下工作,以防止中间人攻击,确保数据传输的安全性。
- 生命周期:包括安装(install)、激活(activate)和等待(waiting)、运行(active)几个阶段。在安装阶段可以预先缓存静态资源;激活阶段可以清理旧缓存;等待阶段则是准备接管页面的网络请求;激活后则可以监听并处理各种事件。
- 缓存与网络代理:通过监听fetch事件,Service Worker可以决定是使用缓存的资源响应请求,还是从网络获取最新资源。这使得开发者能够实现离线访问、提升加载速度和减少网络流量消耗。
- 更新机制:当Service Worker的脚本文件发生变化时,浏览器会自动尝试更新Service Worker。通过利用这种机制,可以实现Web应用的静默更新,即在用户下次访问应用时,自动使用最新的Service Worker脚本和缓存策略。
- 推送通知与后台同步:除了缓存管理,Service Worker还可以用来实现Web推送通知功能,即使用户未打开网站也能接收到通知。此外,它还支持后台同步,允许应用在满足特定条件(如网络恢复)时在后台同步数据。
//(例如: index.js)
if ('serviceWorker' in navigator) {window.addEventListener('load', function() {navigator.serviceWorker.register('/service-worker.js') // 注意这里的'service-worker.js'是Service Worker的文件路径.then(function(registration) {console.log('Service Worker 注册成功,Scope:', registration.scope);}).catch(function(error) {console.log('Service Worker 注册失败:', error);});});
}// service-worker.js 中的代码
self.addEventListener('install', function(event) {event.waitUntil(caches.open('my-cache-v1') // 'my-cache-v1' 是缓存的名称,可以根据需要更改.then(function(cache) {return cache.addAll(['./index.html','./styles.css', // 示例静态资源路径,根据实际情况添加'./script.js', // 示例静态资源路径,根据实际情况添加]);}));
});self.addEventListener('fetch', function(event) {event.respondWith(caches.match(event.request).then(function(response) {// 如果缓存中有匹配的响应,则直接使用缓存的响应if (response) {return response;}// 否则,去网络请求数据return fetch(event.request);}));
});
这段代码首先在网页加载时尝试注册Service Worker,并指定了Service Worker脚本的路径。接着,在service-worker.js中,我们定义了在Service Worker安装时执行的逻辑,即打开一个名为my-cache-v1的缓存并预加载指定的资源文件。最后,通过监听fetch事件,实现了对网络请求的拦截,优先尝试从缓存中返回资源,如果没有命中缓存则通过网络获取资源。