进程与线程
1 进程
CPU是计算机的核心 承担所有计算任务 进程就是cpu资源分配的最小单位 进程是cpu可独立运行并且有自己的资源空间的任务程序 CPU可以有产生多个进程 每产生一个进程都会分配资源空间 每个进程都是互相独立的 。 一个启动其他的进程都处于非运行状态 如果处理多进程启动 CPU使用时间片轮转调度算法实现多进程操作
1.2 线程
线程是cpu调度的最小单位 线程是建立在进程基础上运行的单位,线程是程序中的一个执行流,一个进程可以有多个线程 一个进程只有一个执行流的时候叫做单线程 一个进程有多个执行流的时候叫做多线程 也就是说一个程序可以创建单个或多个执行的线程完成任务
1.3 线程与进程的区别
进程是cpu的分配的最小单位,线程是程序执行的最小单位.线程可以理解为进程中代码执行的分支路线 进程之间是互相独立的 一个进程中各个线程都是共享程序的内存空间的(包括代码,数据集,堆等)以及进程级的资源程序(打开文件和信号) 调整和切换:线程的上下文切换要比进程快,也就是线程之间切换速度要比进程快
1.4 多线程和多进程
多进程:多进程指的是在同一时间 同一计算机系统内允许两个以及两个以上的进程进行运动状态,多线程的好处比如 可以边打开网易云边写代码用vscode,在这里网易云是一个单独进程,vscode也是一个单独的进程 多线程:就是在程序中(进程中)允许执行多个执行流,也就是一个进程可以多个线程来完成不同的任务
1.5 为什么js是一个单线程
js的单线程是因为它的作用有关,作为一个浏览器的脚本语言js主要是与用户交互以及操作dom进行渲染,不会造成异步的复杂麻烦问题,比如js有两个线程 一个在某个dom节点添加了一些内容 ,另一个线程在同一时间删除了这个节点,他没有一个执行顺序,这时候浏览器不知道优先顺序 所以这个问题就决定js线程必须是个单线程 js还有个worker多线程 利用的是多核cpu计算能力。html5也提出了Worker的标准,允许js脚本可以创建多个线程,但是子线程完全受主线程的控制并且不可以操作dom Worker线程 Worker线程是js主线程外的另一个线程,他是独立于浏览器线程的一个后台自动运行的js,用于提高浏览器性能,他的作用就是在js代码主线程执行中,客户端通过操作页面,这段期间页面是不会响应的,直到js脚本执行完毕 他才会有响应,而worker就是保存这段时间操作页面的代码在后台进行运行,这样即使js代码正在运行中,Worker线程也会同步进行中
浏览器
2.1浏览器包括哪些进程
Browser进程
- 浏览器的主线程(负责协调 主控) 该线程只有一个
- 负责浏览器界面的显示 与用户交流(前进,后退)
- 负责各个页面的管理,创建和销毁其他进程(每一个tab页面都是一个进程 创建和销毁指的是tab)
- 将渲染(Renderer)进程得到的内存中的Bitmap(位图)绘制到用户页面上(也就是咱们的真实dom节点上)
- 网络资源下载 管理等
第三方插件进程
- 每个类型插件对应着一个进程 当使用该插件的时候创建进程
GPU进程
- 该进程用于3d绘制等等
渲染进程
- 也就是咱们说的浏览器内核(Renderer进程 内核是多线程)
- 每个Tab都有一个自己的渲染进程 互不影响
- 主要用于页面渲染 脚本执行 时间处理 至于为啥浏览器是一个多进程? 因为如果是单进程的话 每一个tab页(一个进程)崩溃了 会导致其他进程也无法使用 所以浏览器是一个多进程 但是多进程也代表着内存等资源的损耗比较大 渲染进程 主要是页面的渲染 js的执行事件的循环 都在这个进程进行 所以渲染进程是一个很重要的进程
2.2渲染进程Renderer的主要线程
- GUI渲染线程 负责渲染浏览器界面的解析html css 构建dom 和RenderObject树 布局 绘制等 (因为dom树他不会直接进行渲染 他会出现一个Render树 而 RenderObject树 就是render相对应)
- 解析html代码(html代码本质也是字符串) 转换成浏览器的节点形成dom树
- 解析css代码 生成cssom(css规则树)
- 把Dom树和cssom树 结合在一起 形成render树(渲染树) 当我们修改了元素或背景色 页面都会重新绘制(Repaint) 当我们修改了元素的尺寸 页面都会回流 当页面需要执行Repaint和Reflow那就会执行GUI渲染线程 绘制页面 Repaint和Reflow的成本比较高 所以我们要避免Repaint和Reflow **GUI渲染线程和JS引擎线程是互斥的
- 当js线程执行时 GUI线程会被挂起(相当于冻结 不执行 停止)
- **GUI线程执行时候会放在一个等待队列里面 等js线成功执行完空闲的时候会立即执行
- js引擎线程 js线程也就是js内核 负责处理js脚本程序代码(V8引擎就是干这个的他负责解析js代码 运行代码 js线程会一直等待任务队列的代码 然后加以处理
- 一个浏览器只能有一个js线程 所以叫做单线程
- 一个Tab页(Render进程)也只能有一个js线程
- js线程和GUI线程是互斥的 js线程会堵塞GUI线程
- 我们遇见的js执行时间过程 造成页面渲染不连贯导致页面渲染慢
- 事件触发线程
- **他是属于浏览器的线程 不是js线程中的 他是控制事件循环 并且管理着事件队列(task queue)
- 当js执行到事件绑定和一些异步操作 会走事件触发线程将对应的事件添加到它所对应的线程中 等异步操作有了结果 将他们的回调时间添加到事件队列队尾中 等待js线程有空 再处理它们 4.定时器触发线程
- setInterval和setTimeout所在的线程 他们俩并不在js线程中(因为js线程是个单线程 这样会堵塞每一秒的计算影响计数的准确性)
- 通过一个单独的线程来保管(当计数完毕后会添加到事件触发线程的时间队列里,等js线程忙完了 后去执行)
- 在w3c的html规范中 setTimeout中底与4ms事件间隔都是4ms(0就是立即生效) 异步http请求线程
- 在XMLHttpRequest中连接后 通过浏览器开启了一个新的异步http请求线程
- 当检测状态变更 如果有回调函数 异步线程就会产生状态变更时间收到响应函数(准确说是http状态变化)并且把回调函数放在事件队列里 等到js线程有空去执行
2.3 事件循环(Event Loop)
js是分同步任务和异步任务 同步任务都会在主线程中(js线程中)会形成一个执行栈 主线程之外 还有一个事件处罚线程管理的一个任务队列 只要异步操作又了结果 他就会加入事件队列里面 一旦执行栈中所有同步操作执行完了 系统就会读取时间队列 将所有可执行的任务都加入到执行栈开始执行
console.log('我是定时器回调');
};
let httpCallback = function() {
console.log('我是http请求回调');
}
// 同步任务
console.log('我是同步任务1');
// 异步定时任务
setTimeout(setTimeoutCallBack,1000);
// 异步http请求任务
ajax.get('/info',httpCallback);
// 同步任务
console.log('我是同步任务2');
因为定时任务和http请求都是宏任务 具体他俩谁先执行 得根据事件判断
\
首先执行console.log('我是同步任务1'),
其次遇见了异步定时任务 把它交给了定时任务线程 通知定时任务线程1s后将回调函数setTimeoutCallBack()交给事件触发线程处理 1s后事件触发线程将收到他的setTimeoutCallBack()「回调函数」放在他所管理的事件队列中等待执行
接着执行异步http请求 把它交给异步http请求线程发送网络请求 将他的回调函数交给事件触发线程 然后放在事件队列中等待执行
再执行console.log('我是同步任务2');
这个时候js线程已经空闲了 他就会询问事件触发线程中的事件队列是否有执行的回调函数 如果有把它放在执行栈中开始执行 如果没有 js线程会一直发起询问 知道有为止
在这个过程中 每一个线程都很单一切独立进行 **每一个线程只会关心自己的任务是否完成 然后有了回调函数 把它交给事件触发线程的事件队列里面
而js线程只会执行 执行栈中的代码 执行完毕后会重新在访问事件队列中事件并添加到执行栈中 这样反反复复的执行 就是所谓的事件循环(Event Loop) 再次我就引用另外一幅图了
首先执行栈循序执行 判断是同步还是异步操作 如果同步操作 他将对应的线程回调放在事件触发线程中的事件队列 等待执行,这个执行栈继续执行 当执行栈全部执行完毕 为空的时候 讲去访问任务队列 是否有执行的事件回调 如果有放在执行栈中 继续重复执行 如果没有 将会一直发起访问检查
2.4宏任务与微任务(macrotask与microtask)
宏任务(macrotask)
我们可以将每一次执行栈中所执行的代码看成一次宏任务 由于js线程和GUI线程是互斥的关系 浏览器为了使宏任务和dom任务有序的进行 会在每一个宏任务执行完进行一次GUI线程对页面渲染
宏任务->GUI渲染->宏任务。。。。
常见的宏任务
- 主代码块
- setTimeout
- setTInterval
- setTimmedoate() - node的
- requestAnimationFrame -浏览器的
- ajax
- DOM
- UIrending
微任务 (microtask) ES6引进了promise 所以浏览器引进了一个微任务概念 叫做jbos 微任务是在宏任务执行完毕后 GUI渲染之前进行的一次微任务
在微任务队列中 如果是一个thenable 他会比正常的微任务队列执行顺序往后推后一次 因为thenable是返回的一个函数 这个函数中会有各种耗时操作可能会耽误后续的微任务队列里面的事件所以它往后推一个顺序 让下一个先执行 Promise.resolve() 如果返回的是一个 Promise.resolve 函数会往后多加两次微任务
常见的微任务
- process.nextTick() -node
- Promise.then()
- catch
- queueMicrotask
- finally
- object.observe (原声js检测对象变化的api原理跟v-model差不多 在有变化的时候通知并能检测到)
- MutaionObserver 用于检测dom节点变化的api
// 然后我们对他进行观察
Object.observe(model, function(changes){
// 这个异步毁掉函数将会运行
changes.forEach(function(change) {
// 让我们获知变化
console.log(change.type, change.name, change.oldValue);
});
});
线性结构 队列 数组 栈 特征:先进后出 队列可以加很多元素 特征:先进先出 宏任务和微任务注意事项 宏任务和微任务不是在一个任务队列里面 他们都有独自的任务队列 以Chrome为例** 有关渲染的都在渲染线程中进行(dom构建树js解析)需要主线程执行的都在主线程中 而浏览器维护了一套事件循环机制 主线程的任务都会放在消息队列里面执行 主线程每一次都是循环这个消息队列 如果执行过程中出现主线程的其他任务 它会将入消息队列尾端 当执行script脚本 js线程会创建一个全局上下问 在这个全局上下文栈空间维护一个微任务队列 再遇见微任务的时候会把他添加到微任务队列 当js线程执行完会检查微任务队列是否有回调 如果没有则关闭上下文 这就是每一个宏任务都有一个微任务队列 所以也就是说微任务队列先比宏任务队列先执行
上图就是一个完整的过程
首先会将script当成第一个宏任务开始执行 所有代码分为异步和同步 。同步代码会继续往下执行 异步代码会区分是否有宏任务和微任务 宏任务进入事件列表 然后在里面注册回调函数 当他完成后把回调函数加入事件队列里面 微任务进入事件列表 然后在里面注册回调函数 当他完成后把回调函数加入事件队列里面 当主线程微空 会检查两个事件队列是否有任务 并且执行 如果没有进行下一个宏任务 。 这里面执行顺序是先微队列再宏队列 上述过程不断重复 就是一个比较完成的事件循环(Event Loop)
例子1
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log('then4');
});
console.log('then2');
});
then(function () {
console.log('then1');
});
setTimeout(function () {
console.log('setTimeout1');
new Promise(function (resolve) {
resolve();
}).then(function () {
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log('then4');
});
console.log('then2');
});
});
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('then1');
});
setTimeout(function () {
console.log('setTimeout2');
});
console.log(2);
queueMicrotask(() => {
console.log('queueMicrotask1');
});
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log('then3');
});
// promise1
// 2
// then1
// queueMicrotask1
// then3
// setTimeout1
// then2
// then4
// setTimeout2
例子2
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function () {
console.log('setTimeout');
}, 0);
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
});
console.log('script end');
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
例子3
journey
title My working day
section Go to work
Make tea: 5: Me
Go upstairs: 3: Me
Do work: 1: Me, Cat
section Go home
Go downstairs: 5: Me
Sit down: 5: Me
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout0')
}, 0)
setTimeout(function () {
console.log('setTimeout2')
}, 300)
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick1'));
async1();
process.nextTick(() => console.log('nextTick2'));
new Promise(function (resolve) {
console.log('promise1')
resolve();
console.log('promise2')
}).then(function () {
console.log('promise3')
})
console.log('script end')
// script start
// async1 start
// async2
// promise1
// promise2
// script end
// nexttick1
// nexttick2
// async1 end
// promise3
// settimetout0
// setImmediate
// setTimeout2