最近正值秋招,面试了不少前端同窗,感悟颇多,后面我也会在公众号为你们分享下我做为面试官的一些心得,以及对于我常常会问的一些问题的讲解。前端
今天咱们来聊一下浏览器(以Chrome
为例)对线程和进程的调度,这个问题几乎是我每次面试必问的。相信你们都看过不少面经会讲 JavaScript
的执行机制,不少同窗热衷于去背这些面经,以致于连 JavaScript
是单线程的都不知道,就开始回答宏任务、微任务了... 这种我真的特别无语,是真的理解仍是背出来的解题思路其实一看便知了。因此我建议你们不管是准备面试仍是平时积累知识,必定不要太浮躁,要从根本上理解这个问题,而不是去记这些解题思路。web
线程和进程

首先咱们来回顾下线程和进程的概念:面试
-
进程: CPU
进行资源分配的基本单位 -
线程: CPU
调度的最小单位
这是进程和线程最官方也是最多见的两个定义,可是这两个概念太抽象了,很难以理解。通俗一点讲:进程能够描述为一个应用程序的执行程序,线程则是进程内部用来执行某个部分的程序。浏览器
下面再引用一段知乎的高赞回答,我感受很是有意思:安全

作个简单的比喻:进程=火车,线程=车箱微信
-
线程在进程下行进(单纯的车箱没法运行) -
一个进程能够包含多个线程(一辆火车能够有多个车箱) -
不一样进程间数据很难共享(一辆火车上的乘客很难换到另一辆火车,好比站点换乘) -
同一进程下不一样线程间数据很易共享(A车箱换到B车箱很容易) -
进程要比线程消耗更多的计算机资源(采用多列火车相比多个车箱更耗资源) -
进程间不会相互影响,一个线程挂掉将致使整个进程挂掉(一列火车不会影响到另一列火车,可是若是一列火车上中间的一节车箱着火了,将影响到全部车箱) -
进程能够拓展到多机,进程最多适合多核(不一样火车能够开在多个轨道上,同一火车的车箱不能在行进的不一样的轨道上) -
进程使用的内存地址能够上锁,即一个线程使用某些共享内存时,其余线程必须等它结束,才能使用这一块内存。(好比火车上的洗手间)-"互斥锁" -
进程使用的内存地址能够限定使用量(好比火车上的餐厅,最多只容许多少人进入,若是满了须要在门口等,等有人出来了才能进去)-“信号量”
应用程序如何调度进程和线程
当一个应用程序启动时,一个进程就被建立了。应用程序可能会建立一些线程帮助它完成某些工做,但这不是必须的。操做系统会划分出一部份内存给这个进程,当前应用程序的全部状态都将保存在这个私有的内存空间中。网络

当你关闭应用时,进程也就自动蒸发掉了,操做系统会将先前被占用的内存空间释放掉。多线程
一个程序并不必定只有一个进程,进程可让操做系统再另起一个进程去处理不一样的任务。当这种状况发生时,新的进程又将占据一块内存空间。当两个进程须要通讯时,它们进行进程间通信。架构
许多应用程序都被设计成以这种方式进行工做,因此当其中一个进程挂掉时,它能够在其余进程仍然运行的时候直接重启。异步
多进程和多线程
理解了上面的内容,咱们再来从新梳理多进程和多线程的概念:
-
多进程:多进程指的是在同一个时间里,同一个计算机系统中若是容许两个或两个以上的进程处于运行状态。多进程带来的好处是明显的,好比你能够听歌的同时,打开编辑器敲代码,编辑器和听歌软件的进程之间丝绝不会相互干扰。 -
多线程是指程序中包含多个执行流,即在一个程序中能够同时运行多个不一样的线程来执行不一样的任务,也就是说容许单个程序建立多个并行执行的线程来完成各自的任务。
Chrome 的多进程架构
因为浏览器自己没有统一的规范,不一样的浏览器之间的架构可能彻底不一样,在浏览器刚被设计出来的时候,那时的网页很是的简单,每一个网页的资源占有率是很是低的,所以一个进程处理多个网页时可行的。而后在今天,大量网页变得日益复杂。把全部网页都放进一个进程的浏览器面临在健壮性,响应速度,安全性方面的挑战,因此大部分现代浏览器都是多进程的。

从上面的图咱们能够很明显的看出 Chrome
是一个多进程的架构,咱们打开一个浏览器时会启动多个不一样的进程协助浏览器将页面为咱们呈现出来:
-
浏览器进程 -
插件进程 -
GPU进程 -
渲染进程
浏览器进程
浏览器最核心的进程,负责管理各个标签页的建立和销毁、页面显示和功能(前进,后退,收藏等)、网络资源的管理,下载等。
插件进程
负责每一个第三方插件的使用,每一个第三方插件使用时候都会建立一个对应的进程、这能够避免第三方插件crash影响整个浏览器、也方便使用沙盒模型隔离插件进程,提升浏览器稳定性。
GPU进程
负责3D绘制和硬件加速
渲染进程
浏览器会为每一个窗口分配一个渲染进程、也就是咱们常说的浏览器内核,这能够避免单个 page crash
影响整个浏览器。
浏览器内核的多线程
浏览器内核就是浏览器渲染进程,从接收下载文件后再到呈现整个页面的过程,由浏览器渲染进程负责。浏览器内核是多线程的,在内核控制下各线程相互配合以保持同步,一个浏览器一般由如下常驻线程组成:
-
GUI
渲染线程 -
定时触发器线程 -
事件触发线程 -
异步 http
请求线程 -
JavaScript
引擎线程

GUI渲染线程
GUI
渲染线程负责渲染浏览器界面 HTML
元素,当界面须要重绘(Repaint
)或因为某种操做引起回流(reflow
)时,该线程就会执行。
定时触发器线程
浏览器定时计数器并非由 JavaScript
引擎计数的, 由于 JavaScript
引擎是单线程的, 若是处于阻塞线程状态就会影响记计时的准确, 所以经过单独线程来计时并触发定时是更为合理的方案。
事件触发线程
当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件能够是当前执行的代码块如定时任务、也可来自浏览器内核的其余线程如鼠标点击、AJAX异步请求等,但因为JS的单线程关系全部这些事件都得排队等待JS引擎处理。
异步http请求线程
在XMLHttpRequest在链接后是经过浏览器新开一个线程请求, 将检测到状态变动时,若是设置有回调函数,异步线程就产生状态变动事件放到 JavaScript引擎的处理队列中等待处理。
Javascript引擎线程
Javascript
引擎,也能够称为JS内核,主要负责处理 Javascript
脚本程序,例如V8引擎。Javascript
引擎线程理所固然是负责解析 Javascript
脚本,运行代码。
因为 JavaScript
是可操纵 DOM
的,若是在修改这些元素属性同时渲染界面(即 JavaScript
线程和UI线程同时运行),那么渲染线程先后得到的元素数据就可能不一致了。所以为了防止渲染出现不可预期的结果,浏览器设置 GUI
渲染线程与 JavaScript
引擎为互斥的关系,当 JavaScript
引擎执行时 GUI
线程会被挂起, GUI
更新会被保存在一个队列中等到引擎线程空闲时当即被执行。
JavaScript 为什么设计成单线程
从上面咱们了解到 JavaScript
的执行是单线程的,也就是说,同一个时间只能作一件事。那么,为何 JavaScript
不设计成多个线程呢?这样不是效率更高?
做为浏览器脚本语言, JavaScript
的主要用途是与用户互动,以及操做DOM。这决定了它只能是单线程,不然会带来很复杂的同步问题。好比,假定 JavaScript
同时有两个线程,一个线程在某个 DOM
节点上添加内容,另外一个线程删除了这个节点,这时浏览器应该以哪一个线程为准?
因此,为了不复杂性,从一诞生, JavaScript
就是单线程,这已经成了这门语言的核心特征,未来也不会改变。
WebWorker 多线程?
Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程能够执行任务而不干扰用户界面

那么既然 JavaScript
自己被设计为单线程,为什么还会有像 WebWorker
这样的多线程 API
呢?咱们来看一下 WebWorker
的核心特色就明白了:
-
建立 Worker
时,JS
引擎向浏览器申请开一个子线程(子线程是浏览器开的,彻底受主线程控制,并且不能操做DOM) -
JS
引擎线程与worker
线程间经过特定的方式通讯(postMessage API,须要经过序列化对象来与线程交互特定的数据)
因此 WebWorker
并不违背 JS引擎是单线程的
这一初衷,其主要用途是用来减轻cpu密集型计算类逻辑的负担。
本文分享自微信公众号 - 全栈大佬的修炼之路(gh_7795af32a259)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。