「前端进阶」一篇文章带你理解进程和线程

331 阅读7分钟

引言


相信对于大部分前端同学来说,区分进程和线程等概念是一件再痛苦不过的事情了。但是在面试时说不准就会被问到这个问题,所以我们还是需要静下心来细细琢磨😭。

俗话说”好记性不如烂笔头“,所以我觉得自己整理一下这部分有关的知识点,方便自己强化记忆。同时也希望本篇文章对大家也有所帮助。

码字不易,且看且珍惜,点个赞叭⭐️~

正文


简单区分进程 & 线程

1. CPU => 城市
    - CPU 是计算机中的核心,承担所有的计算任务。
    
2. 进程 => 城市中的工厂
    - 工厂(进程)具有自己独立的内存、资源等。
    - 每个工厂(进程)互不影响 
    
3. 线程 => 工厂中的工人
    - 单线程:工厂(进程)中只有一个工人(线程)  
      多线程:工厂(进程)中有多个工人(线程)一起配合完成工作
    - 同一工厂(进程)下的工人(线程)共享同一工厂资源(代码段、数据集、堆等)

小结:

  • 进程: 进程是CPU资源分配的最小单位。(拥有资源和独立运行的最小单位)

  • 线程: 是CPU调度的最小单位。(线程是进程基础上的最小单位)

    • 一个线程一定属于一个进程;
    • 一个进程中可以有很多线程;
    • 各个进程互不影响; 进程中的线程挂掉会影响到整个进程挂掉;

浏览器多进程架构

首先,计算机是多进程架构。多进程的好处体现在:例如你可以边使用QQ音乐听歌,边用编辑器敲代码。两个进程互不影响,如果QQ音乐卡死并不会影响到编辑器。

再来看浏览器,目前的 Chrome浏览器使用多进程来隔离不同的网页

可以通过Chrome 右上角操作菜单 -> 更多工具 -> 任务管理器查看所有进程。

img.png

为什么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)可以为我们解决这个问题:deferasync

  • defer属性告诉浏览器不要等待脚本,浏览器将继续处理 HTML,构建 DOM。脚本会“在后台”下载,然后等 DOM 构建完成后,脚本才会执行。
  • async特性与 defer 有些类似,但是async不会等待任何脚本,加载完毕就执行。不会像defer一样,等待 DOM 构建完成后,脚本才会执行。
顺序DOMContentLoaded
async加载优先顺序。
脚本在文档中的顺序不重要 —— 先加载完成的先执行
不相关。可能在文档加载完成前加载并执行完毕。如果脚本很小或者来自于缓存,同时文档足够长,就会发生这种情况。
defer脚本在文档顺序中的优先
脚本在文档中的顺序非常重要,即使后面的脚本先加载完毕,也会等待前面的加载完毕后再执行
在文档加载和解析完成之后(如果需要,则会等待),即在 DOMContentLoaded 之前执行。

| 定时触发线程

浏览器计数器(setTimeoutsetInterval)并不是由JS引擎进程计数的,因为JS引擎是单线程的,如果处于堵塞进程就会影响计时器的准确性,所以需要单启一个线程来计时更为准确。

| 异步http请求线程

XMLHttpRequest在连接后通过浏览器新开启一个线程请求,当异步请求状态发生改变时,将其对应的回调函数放置到JS引擎的处理队列中等待处理。

| 事件触发线程

当一个事件被触发时该线程会将事件添加到待处理队列的队尾,等待JS引擎的处理。(事件:鼠标点击、AJAX请求等).

写在最后

本文只是粗略的介绍讲解了线程、进程之间的区别以及联系,其中一些内部的知识点并没有扩展开来讲解,如果大家对内部深度实现感兴趣的话,可以自行去查阅有关的知识点~

看到这里,希望本文对你有一些帮助😁。如果文章中有错误,麻烦评论指出,一起进步~~~~。

我是抹茶,不断学习的一名coder✌🏻。