hawx1993 / tech-blog

📦My personal tech blog,not regularly update
http://sf.gg/u/trigkit4/articles
339 stars 30 forks source link

JavaScript单线程特性与浏览器进程的理解 #26

Open hawx1993 opened 1 year ago

hawx1993 commented 1 year ago

什么是进程与线程?

首先,我们来思考个问题:Chrome 浏览器是多进程还是单进程,是多线程还是单线程?

我们可以打开mac的活动监视器,如下图: image.png

从上图我们可以看出Chrome浏览器是一个多进程且多线程的复杂应用,那么什么是线程,什么是进程呢?

浏览器进程

单个网页tab是进程还是线程?

浏览器从关闭到启动,然后新开一个页面至少需要:1个浏览器主进程1个GPU进程1个网络进程多个渲染进程,和多个插件进程。Chrome会尽可能为每一个tab甚至是页面里面的每一个iframe都分配一个单独的进程。

所以,单个网页tab是进程,内部分了若干个线程。

那么,参与到网页渲染生成流程中,有哪些线程?

大概有如下几个主要的线程:

JS分为同步任务和异步任务,同步任务都在主线程上执行,形成一个执行栈(JS引擎线程)。 主线程之外,事件触发线程维护任务队列,异步任务有了回调,就在队列中插入一个事件 一旦执行栈中所有同步执行完成,系统就会读取任务队列,并且把可运行的异步任务转移到可执行栈中。

多进程的好处

🤔思考一下,Chrome浏览器为什么要使用多进程架构呢?

举个例子,假如你有三个tab,你就会有三个独立的渲染进程。当其中一个tab的崩溃时,你可以随时关闭这个tab并且其他tab不受到影响。可是如果所有的tab都跑在同一个进程的话,它们就会有连带关系,一个挂全部挂。

渲染器进程

渲染进程负责标签(tab)内发生的所有事情。在渲染进程里面,主线程(main thread)处理了绝大多数你发送给用户的代码。如果你使用了web worker或者service worker,相关的代码将会由工作线程(worker thread)处理。合成(compositor)以及光栅(raster)线程运行在渲染进程里面用来高效流畅地渲染出页面内容。

渲染进程的主要任务是将HTML,CSS,以及JavaScript转变为我们可以进程交互的网页内容

为了防止渲染出现不可预期的结果,浏览器设置 GUI 渲染线程与 JavaScript 引擎为互斥的关系。当 JavaScript 引擎执行时, GUI 线程会被挂起,GUI 更新会被保存在一个队列中等到引擎线程空闲时立即被执行。

为什么JavaScript是单线程?

由上文可知,浏览器不是单线程的,浏览器的内核是多线程的,但为什么JavaScript是单线程的呢?

单线程的意思就是同一个时间内只能做一件事,JavaScript主要用户是于用户互动以及操作DOM。 这决定了他只能是单线程,否则会带来很多问题。比如:

假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

js是单线程的原因不是js语言的特性,是浏览器只给js分配了一个线程的原因;

单线程的缺点是什么

Web Workers始终只能由一个主线程去更新页面,因为Web Workers内代码不能操作DOM更新UI(workers内无window)

Q1:JavaScript是单线程,怎样执行异步的代码?

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。js引擎执行异步代码而不用等待,是因有为有 消息队列和事件循环。

消息队列:消息队列是一个先进先出的队列,它里面存放着各种消息。
事件循环:事件循环是指主线程重复从消息队列中取消息、执行的过程。

Q2:JavaScript 引擎怎么知道异步任务有没有结果,能不能进入主线程呢?

答案是js引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务(异步任务(各种浏览器事件、定时器等)都是先添加到“任务队列”中。),是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环。

除了广义的同步任务和异步任务,JavaScript 单线程中的任务可以细分为宏任务和微任务。

每个浏览器环境,至多有一个event loop。一个event loop可以有1个或多个task queue(任务队列),任务又分为Micro Task(微任务,比如:process.nextTick,Promise)和Macro Task(宏任务,比如:I/O,UI rendering,setTimeout和setInterval,requestAnimationFrame)——按执行先后顺序排序。

第一次事件循环中,JavaScript 引擎会把整个 script 代码当成一个宏任务执行,执行完成之后,再检测本次循环中是否存在微任务,存在的话就依次从微任务的任务队列中读取执行完所有的微任务,再读取宏任务的任务队列中的任务执行,再执行所有的微任务,如此循环。JS 的执行顺序就是每次事件循环中的宏任务-微任务。

image.png

每次微任务执行之后宏任务执行之前,如果有UI操作页面会重新渲染,一般页面刷新率是60HZ/秒,一帧是16.6毫秒,所以可以理解为事件循环每次轮询的时间大概是16.6毫秒

React 16实现了新的调度策略(Fiber), 新的调度策略提到的异步、可中断,其实就是基于浏览器的 requestIdleCallback和requestAnimationFrame两个API。

Q3:setTimeout函数哪怕设置执行的时间为0,看起来依然像延迟执行一样,是因为异步?还是放到了js其他线程执行了?

setTimeout函数是放在定时触发器线程的,当计时结束,开始执行回调函数的时候,是把代码放到js队列里面,而js是按照队列执行的,所以即使是setTimeout函数哪怕设置执行的时间为0,也不会立刻执行,因为要按照队列执行,这个不是异步的原因。