基本概念
进程是CPU资源分配的最小单位,线程是CPU调度的最小单位。
进程有单独的属于自己的内存空间,一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线。一个进程的内存空间是共享的,每个线程都可用这些共享内存。
翻译翻译:
如果把进程比作工厂的话,线程就是工厂的工人,工厂有单独的专属于自己的工厂资源,工人可以共享工厂资源。
多进程的好处
在同一个时间内,同一个计算机系统中允许两个或两个以上的进行处于运行状态,进程之间丝毫不会互相干扰,可以同时做多个事情。
多线程的好处
程序中包含多个执行流,即在同一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
常见进程
以chrome浏览器为例,来分析一下浏览器的进程。
上图是浏览器只打开一个百度的标签页时,任务管理器的状态。主要可以关注以下几个进程。
- 浏览器进程:负责协调、主控其他进程。
- 负责各个页面的管理,创建和销毁其他进程;
- 负责浏览器界面显示与用户交互,如前进、后退等;
- GPU进程:使用初衷是为了实现3D CSS的效果,后面随着网页、Chrome的UI界面都选择用GPU来绘制,使得GPU成为了浏览器普遍的需求。
- Network Service:网络进程,主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面。
- Storage Service:控制文件读写的存储线程;
- Audio Service:音频进程;
- 渲染进程:核心任务是将HTML、CSS和javascript转换为用户可以与之交互的网页,排版引擎Blink和javascript引擎V8都是运行在该进程中。默认情况下,Chrome会为每个Tab标签创建一个渲染进程,当进程数达到一定的界限后,Chrome会将访问同一个网站的tab都放在一个进程里面跑。处于安全考虑,渲染进程都是运行在沙箱模式下。渲染进程中主要包含以下线程:
- 主线程 Main thread:对几个常驻线程的调用,执行大多数的代码。
- 工作线程 Worker thread:web worker和service worker相关的代码由该进程处理。
- 光栅线程 Raster thread:将文档结构、元素的样式、元素的几何位置以及绘画顺序这些信息转化为显示器的像素的过程叫做光栅化。
- 合成线程 Compositor thread:将页面分成若干层,分别进行光栅化,光栅化之前合成线程需要将页面的一层切分成一块又一块小图块,光栅线程会栅格化每个图块并将它们储存在GPU的内存中。最后在合成线程中合并成一个页面。当页面的层超过一定的数量后,层的合成操作要比每个帧中光栅化页面的一小部分还要慢。
- 插件进程:虽然图上没有,也可以了解一下。主要负责插件的运行,用来保证插件进程崩溃不会对浏览器和页面造成影响。
⚠️Network Service、Storage Service和Audio Service这些服务本来是在浏览器进程里面的,后来将这些模块拆分为了一个个不同的服务,这个过程也叫做Chrome服务化。服务化之后,这些功能既可以放在不同的进程里运行,也可以合并为一个单独的进程运行。
这样做主要是为了让Chrome在不同性能的硬件上有不同的表现。当Chrome运行在一些性能比较好的硬件时,浏览器进程香港的服务会放在不同的进程中运行来提高系统的稳定性。如果硬件性能不好,这些服务就被放在同一个进程里面执行来减少内存的占用。
渲染进程中的线程
渲染进程也称为浏览器内核,浏览器内核通过取得页面内容,整理信息、计算组合最终输出可视化的图像结果,通常也被称为渲染引擎。
浏览器内核是多线程,在内核控制下各个线程相互配合,一个浏览器通常由以下常驻线程组成:
- GUI渲染线程(有且只有一个)
- JavaScript引擎线程(有且只有一个)
- 定时触发器线程(多个)
- 事件触发线程
- 异步http请求线程(多个)
GUI渲染线程
主要负责页面的渲染,解析HTML、CSS,构建DOM树,布局和绘制等。当界面需要重绘或重排时,会执行该线程。
⚠️渲染线程跟JS引擎线程互斥,当执行JS引擎线程时,GUI渲染会被挂起。当任务队列空闲时,才会执行GUI渲染。互斥的原因是因为,JS是可以操作DOM的,边操作边渲染会出现问题,js被设计成单线程也是这个原因。
JS引擎线程
同步任务和异步任务都由该线程执行。如果JS引擎执行脚本的时间过长,将导致页面渲染阻塞。
定时器触发线程
主要负责执行异步定时器一类的函数,如setTimeOut、setInterval。主线程按顺序执行代码时,遇见定时器,会将定时器交给该线程处理。当计时完毕后,通过事件触发线程将技术完毕后的事件加入到任务队列的尾部,等待JS引擎线程执行。
事件触发线程
主要负责将准备好的事件交给JS引擎线程执行。也就是将已经准备好的异步回调函数添加到任务队列的队尾。
异步http请求线程
负责执行异步请求一类的函数,如: Promise、axios、ajax等。主线程按顺序执行代码时,遇到异步请求,会将函数交给该线程处理。当监听到状态码变更,如果有回调函数,事件触发线程将回调函数加入到任务队列的尾部,等待JS引擎线程执行。