引言
相信对于大部分前端同学来说,区分进程和线程等概念是一件再痛苦不过的事情了。但是在面试时说不准就会被问到这个问题,所以我们还是需要静下心来细细琢磨😭。
俗话说”好记性不如烂笔头“,所以我觉得自己整理一下这部分有关的知识点,方便自己强化记忆。同时也希望本篇文章对大家也有所帮助。
码字不易,且看且珍惜,点个赞叭⭐️~
正文
简单区分进程 & 线程
1. CPU => 城市
- CPU 是计算机中的核心,承担所有的计算任务。
2. 进程 => 城市中的工厂
- 工厂(进程)具有自己独立的内存、资源等。
- 每个工厂(进程)互不影响
3. 线程 => 工厂中的工人
- 单线程:工厂(进程)中只有一个工人(线程)
多线程:工厂(进程)中有多个工人(线程)一起配合完成工作
- 同一工厂(进程)下的工人(线程)共享同一工厂资源(代码段、数据集、堆等)
小结:
-
进程: 进程是
CPU
资源分配的最小单位。(拥有资源和独立运行的最小单位) -
线程: 是CPU调度的最小单位。(线程是进程基础上的最小单位)
- 一个线程一定属于一个进程;
- 一个进程中可以有很多线程;
- 各个进程互不影响; 进程中的线程挂掉会影响到整个进程挂掉;
浏览器多进程架构
首先,计算机是多进程架构。多进程的好处体现在:例如你可以边使用QQ音乐听歌,边用编辑器敲代码。两个进程互不影响,如果QQ音乐卡死并不会影响到编辑器。
再来看浏览器,目前的 Chrome
浏览器使用多进程来隔离不同的网页 。
可以通过Chrome 右上角操作菜单 -> 更多工具 -> 任务管理器
查看所有进程。
为什么Chrome要设计为多进程架构?
在前端刚刚兴起时,网页的内容十分简单,消耗的资源很小,所以将浏览器设计为单进程多进程是可行的。随着需求发展,前端页面展示的功能和用户的交互日益增多,此时 单进程多线程 存在诸多问题:
- 一个
Tab
(网页)崩溃,会影响到所有浏览器页面; - 第三方插件崩溃,也会影响到浏览器;
- 一个进程下的线程会共享资源,会存在安全隐患; 还有其他的优势...
当然,多进程对资源消耗也会更大。
浏览器主要包括哪些进程?
Browser
进程(浏览器主进程,只有一个):- 负责地址栏、书签栏、前进后退等部分工作
- 负责各个
tab
(网页)的管理,创建销毁进程 - 网络资源的管理及下载
Renderer
进程(浏览器核心/浏览器渲染进程):- 负责一个
tab
内关于网页呈现的所有事情、例如脚本执行、页面渲染、事件处理等...
- 负责一个
- 第三方插件进程:
- 每个插件对应一个进程,进程仅在插件使用时创建,如
flash
插件
- 每个插件对应一个进程,进程仅在插件使用时创建,如
GPU
进程:- 最多一个,用于绘制
3D
- 最多一个,用于绘制
我们在这里主要介绍浏览器的Renderer
进程(也叫浏览器核心/渲染进程)。
渲染进程(浏览器核心)
从以上内容我们已经知道,Chrome
会为每个网页单独启用进程,进程直接互不影响,所以每个tab
都有其独立的渲染引擎实例。
浏览器核心是多线程
敲重点:浏览器核心是多线程的。 页面的渲染、js
的执行、事件的循环都在这个进程内进行,接下来列举一些常驻线程:
GUI
渲染线程JS
引擎线程- 事件触发线程
- 异步
http
请求线程 - 定时器触发线程等
| GUI
渲染线程
负责渲染浏览器界面HTML
元素,当页面发生重绘或者回流时,该线程会执行。在js
引擎运行脚本期间,GUI
线程会被挂起。
| JS
引擎线程
JS
引擎,主要负责JS
脚本的运行和处理。
JS
是单线程的?
为什么JS
要被设计为单线程的?
由于JS
这门语言当初设计的初衷就是为了处理用户和网页的交互,如果JS
是多线程的,可能会出现UI
冲突的现象:
例:如果JS
是多线程的,那么可能一个线程是删除DOM
,一个线程是点击DOM
,此时会产生冲突,需要浏览器来裁决哪个线程的优先级更高。
虽然可以通过给线程加锁的方式解决这个问题,但是为了避免引入锁带来的复杂性,JS
在设计当初就采用了单线程执行。
JS
引擎线程与GUI
线程相互互斥!
由于JS
可以操作DOM
节点,如果在运行JS
同时渲染页面(即:JS
引擎线程和GUI
线程同时运行),那么渲染结果将是不可预期的。
为了使得渲染结果符合预期,浏览器设置GUI
线程和JS
引擎是互斥的,当JS
引擎执行时,GUI
线程会被挂起,GUI
更新会被保存到一个队列中,待引擎线程为空后立即被执行
JS
会阻塞页面加载
由上可知,JS
引擎线程与GUI
线程相互互斥,所以在JS
引擎执行时,GUI
会被挂起,待JS
执行完成后,再执行GUI
线程。
因此JS
会阻塞页面渲染,所以当JS
代码运行时间过长时,用户会感到页面不流畅,体验不佳。
<script>
标签阻塞页面渲染
当浏览器遇到脚本外部或内部<script src=""></script>
时:浏览器必须等脚本下载完,并执行结束,之后才能继续处理剩余的页面,如果js
执行的时间过长,会阻塞页面渲染,出现"白屏",降低用户体验。
所以我们一般将script
放在页面的底部,但是这并不是一个完美的解决方案。
由于放在了页面的底部,导致只有当HTML
全部加载完毕后,才会执行到script
,对于很长的HTML
来说,可能会造成明显的延迟。
解决方案: 使用<script>
特性(attribute
)可以为我们解决这个问题:defer
和 async
。
defer
属性告诉浏览器不要等待脚本,浏览器将继续处理HTML
,构建DOM
。脚本会“在后台”下载,然后等DOM
构建完成后,脚本才会执行。async
特性与defer
有些类似,但是async
不会等待任何脚本,加载完毕就执行。不会像defer
一样,等待DOM
构建完成后,脚本才会执行。
顺序 | DOMContentLoaded | |
---|---|---|
async | 加载优先顺序。 脚本在文档中的顺序不重要 —— 先加载完成的先执行 | 不相关。可能在文档加载完成前加载并执行完毕。如果脚本很小或者来自于缓存,同时文档足够长,就会发生这种情况。 |
defer | 脚本在文档顺序中的优先 脚本在文档中的顺序非常重要,即使后面的脚本先加载完毕,也会等待前面的加载完毕后再执行 | 在文档加载和解析完成之后(如果需要,则会等待),即在 DOMContentLoaded 之前执行。 |
| 定时触发线程
浏览器计数器(setTimeout
和setInterval
)并不是由JS
引擎进程计数的,因为JS
引擎是单线程的,如果处于堵塞进程就会影响计时器的准确性,所以需要单启一个线程来计时更为准确。
| 异步http
请求线程
在XMLHttpRequest
在连接后通过浏览器新开启一个线程请求,当异步请求状态发生改变时,将其对应的回调函数放置到JS
引擎的处理队列中等待处理。
| 事件触发线程
当一个事件被触发时该线程会将事件添加到待处理队列的队尾,等待JS
引擎的处理。(事件:鼠标点击、AJAX
请求等).
写在最后
本文只是粗略的介绍讲解了线程、进程之间的区别以及联系,其中一些内部的知识点并没有扩展开来讲解,如果大家对内部深度实现感兴趣的话,可以自行去查阅有关的知识点~
看到这里,希望本文对你有一些帮助😁。如果文章中有错误,麻烦评论指出,一起进步~~~~。
我是抹茶,不断学习的一名coder
✌🏻。