进程、线程
一个进程就是一个程序的运行实例。详细解释就是,启动一个程序的时候,操作系统会为该程序创建一块内存,用来存放代码、运行中的数据和一个执行任务的主线程,把这样的一个运行环境叫进程。
性质
进程是操作系统进行资源分配的基本(最小)单位,线程是CPU 执行运算和调度的基本单位
归属
一个操作系统中可以有很多进程,一个进程可以有很多线程
开销
进程创建、销毁和切换的开销都要远大于线程
拥有资源
每个进程都拥有自己的内存和资源,一个进程中的线程会共享这些内存空间(包括代码段、数据集、堆等)和资源(打开文件和信号)
通信方式
进程之间可以通过IPC 机制实现通信,线程之间主要通过共享变量及其变种形式实现通信
控制和影响能力
子进程无法控制父进程,一个进程发生异常时一般不会影响其他进程;子线程可以控制父线程,如果主线程发生异常,会影响其所在进程和其余线程
扩展能力
多进程可以方便地扩展到多机分布式系统上,多线程想要扩展到多台机器上就很困难
CPU 利用率
进程的CPU 利用率低,因为需要额外的上下文切换开销;线程的CPU 利用率高,因为切换简单
可靠性
进程的可靠性要高于线程
如果进程之间需要进行数据的通信,需要使用用于进程间通信(IPC)的机制。IPC 是一个统称,指可用于不同进程之间通信的手段。Chromium 最新使用的是 mojo(IPC 跨平台框架)。
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1 把数据从用户空间拷到内核缓冲区,进程2 再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication) 。
进程间通信方式:
- 管道
- 信号
- 消息队列
- 共享内存
- 信号量
浏览器的多进程 - Chrome
- 浏览器是多进程的
- 浏览器之所以能够运行,是因为系统给它的进程分配了资源(cpu、内存)
- 每打开一个Tab 页,就相当于创建了一个独立的浏览器进程(浏览器有优化机制,有些进程可能被合并,一个Tab标签对应一个进程并不一定是绝对的)
多进程优点
- 避免单个page crash 影响整个浏览器
- 避免第三方插件crash 影响整个浏览器
- 多进程充分利用多核优势
- 方便使用沙盒模型隔离插件等进程,提高浏览器稳定性
主要进程
- Browser 进程(控制进程):负责协调和管理所有进程,例如创建、销毁和分配其他进程,处理用户输入和操作系统通信等。
- 插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
- GPU 进程:最多一个,用于3D 绘制、页面合成等等
- 网络进程:负责处理网络请求,例如 HTTP 请求、DNS 查询和 TCP 连接等
- Renderer 进程(浏览器内核)(内部是多线程的):默认每个Tab 页面一个进程,互不影响。主要作用:页面渲染,脚本执行,事件处理等
浏览器进程 - 主进程
包括以下线程:
-
控制线程(Browser Control Thread):主要负责协调浏览器进程中其他各个线程的工作
-
负责处理用户与浏览器界面之间的交互事件
例如,当用户在浏览器中点击了一个链接,这个事件会被浏览器控制线程接收并处理,然后它会通知网络进程去请求对应的资源,通知渲染线程去渲染页面等等。
-
还负责处理一些浏览器全局状态的更新,例如历史记录、cookie、浏览器设置等。
-
它还会处理浏览器进程级别的一些操作,例如弹出对话框、创建新窗口等等。
-
-
定时器线程
- 定时器线程主要负责执行与浏览器相关的定时器任务,比如刷新页面、处理媒体数据等。
- 在浏览器进程中,定时器线程会维护一个定时器队列,它记录了所有定时器的信息,如回调函数、延迟时间等。
- 一旦定时器被触发,它会通知渲染进程中的 JavaScript 引擎线程,引擎线程会在恰当的时间重新激活渲染进程中的事件循环,执行定时器的回调函数。
-
存储线程(Storage Thread):负责处理本地存储相关的操作,例如cookie和Web Storage等。
渲染进程 - 浏览器内核
浏览器的渲染进程(渲染引擎/排版引擎blink)是多线程的。
页面的渲染,JS 的执行,事件的循环都在这个进程内进行。
主要常驻线程:
【版本1】
-
GUI 渲染线程
- 解析 HTML 和 CSS,构建 DOM 树和渲染树
- 根据渲染树进行布局计算和绘制命令的生成。生成绘制命令后,GUI 线程会将这些命令发送到GPU 进程,由GPU 进程将这些命令转化为GPU 可以执行的指令,并生成位图。
- 执行 JavaScript 代码,处理页面的交互逻辑:当JS代码需要执行时,JS引擎线程会被唤醒,执行完毕后再将结果返回给渲染进程主线程,由渲染进程主线程进行后续的处理。
- 处理浏览器界面的现实,包括地址栏、菜单、按钮、输入框等
- 处理和其他进程通信的消息和事件,例如浏览器进程发来的新页面请求、渲染进程发来的鼠标事件等。
- GUI 线程与JS 引擎线程是互斥的,当JS引擎线程需要执行一些较为耗时的任务时,如果不采用异步编程的方式,就会导致页面的渲染和交互出现卡顿现象。
-
JS 引擎线程
- 也称为JS 内核,负责处理Javascript 脚本程序(例如V8 引擎)
- JS 引擎线程负责解析Javascript 脚本,运行代码
- JS 引擎一直等待着任务队列中任务的到来,然后加以处理
-
事件触发线程
- 归属于浏览器而不是JS 引擎,用来控制事件循环(JS 引擎自己忙不过来,需要浏览器另开线程协助)
- 当JS 引擎执行代码块如setTimeOut 时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX 异步请求等)会将对应任务添加到事件线程中
- 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS 引擎的处理
- 由于JS 的单线程关系,所以这些待处理队列中的事件都得排队等待JS 引擎处理(当JS 引擎空闲时才会去执行)
-
定时器线程(Timer Thread)
- 它只管理与页面渲染相关的定时器。
- 例如,当页面需要执行一个动画时,渲染进程会创建一个定时器,定时器线程会确保在每帧渲染前,动画的状态已经更新。当动画结束时,渲染进程会通知浏览器进程,将定时器从定时器队列中移除。
- 主要负责执行页面中与DOM有关的setTimeout() 和setInterval() 方法。
- 计时完毕后,添加到事件队列中,等待JS 引擎空闲后执行
- 注意,W3C在HTML标准中规定,规定要求setTimeout 中低于4ms 的时间间隔算为4ms。
-
异步http 请求线程
- XMLHttpRequest 在连接后是通过浏览器新开一个线程请求
- 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JS 引擎执行。
-
Web Worker 线程:负责执行在 Web Worker 中创建的 JavaScript 脚本。
【版本2】
- 主线程
- 合成(器)线程
- 光栅线程
渲染进程中的网络模块负责处理该进程中的网络请求,而网络进程负责处理所有浏览器进程中的网络请求。换句话说,渲染进程中的网络模块是网络请求的发起者和处理者,而网络进程则是所有网络请求的协调者和管理者。
Browser 进程和Renderer 进程的通信
浏览器进程和渲染进程的通信主要是通过以下两种方式实现的:
1、管道通信
浏览器进程会通过 IPC 机制在操作系统中创建一个管道,然后将管道的句柄传递给渲染进程。渲染进程通过这个管道发送请求数据给浏览器进程,浏览器进程接收到请求数据后,处理完数据之后,再通过同样的管道将处理结果返回给渲染进程。管道通信的方式具有简单、高效的特点。
2、内存共享
内存共享的方式可以避免数据的序列化和反序列化过程,提高通信效率。在内存共享中,浏览器进程会在操作系统中申请一块内存,然后将这块内存的句柄传递给渲染进程。渲染进程通过这个句柄可以访问这块内存,从而与浏览器进程进行通信。
总之,具体方式的选择取决于数据的大小、通信频率以及性能等因素。
HTML5 Web Worker
- 创建Worker 时,JS 引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作DOM)
- JS 引擎线程与worker 线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据)
如果有非常耗时的工作,请单独开一个Worker 线程,这样里面不管如何都不会影响JS 引擎主线程,只待计算出结果后,将结果通信给主线程即可。Worker 可以理解是浏览器给JS 引擎开的外挂,专门用来解决那些大量计算问题。
Web Worker 与 SharedWorker
-
Web Worker只属于某个页面,不会和其他页面的Render 进程(浏览器内核进程)共享
所以Chrome 在Render 进程中(每一个Tab 页就是一个render 进程)创建一个新的线程来运行Worker 中的JavaScript 程序。
-
SharedWorker 是浏览器所有页面共享的,不能采用与Worker 同样的方式实现,因为它不隶属于某个Render 进程,可以为多个Render 进程共享使用
所以Chrome 浏览器为SharedWorker 单独创建一个进程来运行JavaScript 程序,在浏览器中每个相同的JavaScript 只存在一个SharedWorker 进程,不管它被创建多少次。