Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
hawx1993 opened this issue Jan 4, 2023 · 0 comments
Open

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

hawx1993 opened this issue Jan 4, 2023 · 0 comments

Comments

@hawx1993
Copy link
Owner

hawx1993 commented Jan 4, 2023

什么是进程与线程?

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

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

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

  • 进程是CPU资源分配的最小单位
  • 线程是CPU调度的最小单位

浏览器进程

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

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

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

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

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

  • GUI渲染线程
    • 负责渲染浏览器界面,解析 HTML、CSS,构建 DOM tree和 render tree,布局和绘制等。当需要重绘或者其他操作引发回流时,该线程就会执行。GUI渲染线程与JS引擎线程是互斥的。
  • JS引擎线程
    • 负责解析 JavaScript 脚本,运行代码。
  • 定时器触发线程:
    • setTimeoutsetInterval 所在的线程。浏览器定时计数器并不是由 JS 引擎计数的,因为 JS 是单线程的,如果处于阻塞线程状态就会影响计时的准确,所以通过单独的线程来计时并触发定时更为合理。
  • 异步HTTP请求线程
    • XMLHTTPRequest建立连接后,浏览器新开一个线程请求,一旦检测到状态变更并且设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中,等待 JS 引擎空闲时处理。
  • 事件触发线程
    • 当一个事件被触发时,该线程会把事件添加到待处理队列的队尾,等待 JS 引擎处理。如:Ajax异步请求,定时任务等

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

多进程的好处

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

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

渲染器进程

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

  • Main: 构建dom树 -> 加载资源 -> js下载与执行 -> 样式计算 -> 构建布局树 -> 绘制 -> 创建层树。(注:Main不是一个线程,而是多个线程的集合)
  • Worker thread: Web Worker 运行在这个线程,可能存在多个
  • Compositor thread: 合成器,将层合成帧,分成多个磁贴
  • Raster thread: 栅格化磁贴后交给GPU

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

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

为什么JavaScript是单线程?

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

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

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

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

单线程的缺点是什么

  • 单线程的缺点就是没法充分利用CPU的资源
    • 为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。 所以,这个新标准并没有改变JavaScript单线程的本质。
  • 大量计算占用CPU导致无法继续调用异步I/O,容易造成堵塞,影响后续代码执行。
  • 错误会引起整个应用退出

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,也不会立刻执行,因为要按照队列执行,这个不是异步的原因。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant