引言
相信对于大部分前端同学来说,区分进程和线程等概念是一件再痛苦不过的事情了。但是在面试时说不准就会被问到这个问题,所以我们还是需要静下心来细细琢磨😭。
俗话说”好记性不如烂笔头“,所以我觉得自己整理一下这部分有关的知识点,方便自己强化记忆。同时也希望本篇文章对大家也有所帮助。
码字不易,且看且珍惜,点个赞叭⭐️~
正文
简单区分进程 & 线程
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✌🏻。