一、进程与线程
操作系统会为每个进程分配独立的空间(独立的资源),一个进程由一个或多个线程组成,同个进程下的各个线程之间共享程序的内存空间;
如上图:每个线程都拥有不同的PID(进程ID),比如 Clash for Windows是一个id为29944的线程,
(一)、进程(process)
计算机已运行的程序;进程曾是``分时系统的基本运作单位;在面向线程设计的系统(如当代多数操作系统,Linux 2.6及更新的版本)中,进程本身不是基本运作单位,而是线程的容器。
(二)、线程(thread)
操作系统能够进行运算调度的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
二、单线程与多线程
如果一个进程只有一个线程,我们称之为单线程。单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。
单线程处理的优点:同步应用程序的开发比较容易,但由于需要在上一个任务完成后才能开始新的任务,所以其效率通常比多线程应用程序低。
如果完成同步任务所用的时间比预计时间长,应用程序可能会不响应。针对这个问题,我们可以考虑使用多线程,即在进程中使用多个线程,这样就可以处理多个任务。
众所周知,JavaScript是单线程的。每个窗口一个JavaScript线程,既然是单线程的,在某个特定的时刻,只有特定的代码能够被执行,其它的代码会被阻塞
JS 中其实是没有线程概念的,所谓的单线程也只是相对于多线程而言。JS 的设计初衷就没有考虑这些,针对 JS 这种不具备并行任务处理的特性,我们称之为 “单线程”。
JavaScript是单线程的,但是它的宿主环境浏览器是多线程的;浏览器内核(渲染进程)中除了Javascript引擎线程之外,还含有GUI渲染线程、事件触发线程、定时触发器线程等。
三、浏览器内核
「浏览器最核心的部分是 “Rendering Engine”,即 “ 渲染引擎 ”,不过我们一般习惯将之称为 “ 浏览器内核 ”。」 它主要包括以下线程:
(一)、GU渲染线程
GUI 渲染线程负责渲染浏览器界面,解析 HTML,CSS,构建 DOM 树和 RenderObject 树,布局和绘制等。当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行。
1. 重绘
当页面元素样式改变不影响元素在文档流中的位置时(如background-color, border-color, visibility),浏览器只会将新样式赋予元素并进行重新绘制操作;
当render tree中的一部分或全部因为元素的规模尺寸、布局、隐藏等改变时,浏览器重新渲染部分DOM或全部DOM的过程;
触发回流的操作如下:
- 页面首次渲染
- 浏览器窗口大小发生变化
- 元素尺寸或位置发生变化
- 元素内容变化(文字数量或图片大小等)
- 添加或者删除可见的
DOM元素 - 激活
CSS伪类(:hover) - 查询某些属性或调用某些方法
3. 性能影像
回流比重绘的代价要更高;
即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流
现代浏览器会对频繁的回流或重绘操作进行优化: 浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。
4. 如何避免
-
CSS
- 避免使用table布局
- 尽可能在DOM树的最末端改变class
- 避免设置多层内联样式
- 将动画效果应用到position属性为absolute或fixed的元素上
- 避免使用CSS表达式(例如:calc())
-
JavaScript
- 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性
- 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中
- 也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘
- 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来
- 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流
(二)、JavaScript 引擎线程
JavaScript 引擎线程负责解析 JavaScript 脚本并运行相关代码。 JavaScript 引擎一直等待着任务队列中任务的到来,然后进行处理,一个Tab页(Renderer 进程)中无论什么时候都只有一个 JavaScript 线程在运行 JavaScript程序。
Tips
GUI 渲染线程与 JavaScript 引擎线程是互斥的,所以如果 JavaScript 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染被阻塞。
(三)、事件触发线程
当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待 JavaScript 引擎的处理。这些事件可以是当前执行的代码块如定时任务、也可来自浏览器内核的其他线程如鼠标点击、AJAX 异步请求等,但由于 JavaScript 引擎是单线程的,所有这些事件都得排队等待 JavaScript 引擎处理。
(四)、定时触发器线程
浏览器定时计数器并不是由 JavaScript 引擎计数的,这是因为 JavaScript 引擎是单线程的,如果处于阻塞线程状态就会影响记计时的准确,所以通过单独线程来计时并触发定时是更为合理的方案。我们日常开发中常用的 setInterval 和 setTimeout 就在该线程中。
(五)、Http异步请求线程
在 XMLHttpRequest 在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript 引擎的处理队列中等待处理。
由于
JavaScript引擎与GUI渲染线程是互斥的,如果JavaScript引擎执行了一些计算密集型或高延迟的任务,那么会导致GUI渲染线程被阻塞或拖慢。那么如何解决这个问题呢? Web Workers。
四、Web Workers
web worker允许一段Javascript程序运行在主线程之外的另一个线程中、Web Worker的作用,就是为了JavaScript创造多线程环境,允许主线程创建Worker线程,将一些任务分配给后者运行 ;
(一)、web worker Api
web worker的环境与window不同、无法在web Worker线程中直接操作Dom元素,还有些许window对象的某些方法和属性;
Web Worker所支持的API:
- Cache
- CustomEvent
- Fetch
- Promise
- FileReader
- IndexedDB
- WebSocker
- XMLHttpRequest
(二)、主线程与 Web Worker之间的通信
主线程和 Worker 线程相互之间使用postMessage()方法来发送信息,并且通过 onmessage这个事件处理器来接收信息。数据的交互方式为传递副本,而不是直接共享数据。主线程与 Worker 线程的交互方式如下图所示:
vue项目中如下:
- 安装
worker-loader
npm i worker-loader -S -D
- 配置
worker-loader
vue.config.js的webpack中配置如下:
module.exports = {
configureWebpack: {
module: {
rules: [
// web-worker 的配置
{
test: /.worker.js$/,
use: {
loader: 'worker-loader',
// 允许将内联的 web worker 作为 BLOB
options: { inline: 'no-fallback' }
}
},
// js的配置
{
// 用来匹配文件名称的
test: /.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
}
}
- 定义
my.worker.js文件,文件夹后缀命明如配置文件所写,为的解析;
// my.worker.js
function fib(n) {
if (n < 3) {
return 1
} else {
return n * 5
}
}
// 接收数据
onmessage = function (event) {
console.log('分线程', event.data, typeof event.data)
const res = fib(event.data || 0)
// const res=1200
// 传输数据
postMessage(res)
}
vue文件中启用web-worker
import Worker from './test.worker'
export default {
name: 'home',
data() {
return {
worker: null
}
},
mounted() {
const a = 1
this.worker = new Worker()
console.log(this.worker)
this.worker.postMessage(a)
// 注册监听函数,接收子线程消息
this.worker.onmessage = (event) => {
console.log(111111)
console.log(event.data)
}
}
}
</script>
web-worker的postMessage传输的值必须是可复制的值,并且只能传一个实参;