CPU、进程、线程之间的关系
-
cpu是计算机的核心,承担了所有计算机的任务 -
进程cpu资源分配的最小单位 -
线程cpu调度的最小单位
三者之间的的关系在之前看到的一篇文章里说就好比一个工厂,这个工厂就是cpu。
工厂每次只能提供一个车间的电力,也就是当一个车间开工的时候,其它所有车间不能工作。
这就意味着一个cpu一次只能运行一个任务,而进程就是这一个个车间。任何时刻,cpu都只能进行一个进程,其他进程就进入了非运行状态。
每个车间里面有很多的工人,这些工人共享一个车间。
这就相当于一个进程可以存在多个线程,多个线程共享进程的资源。
浏览器是多进程的
浏览器是多进程的,大家可以打开任务管理器,在进程中看看Chrome浏览器(或任意浏览器打开多个tab页面):
浏览器进程分类
- Browser进程
- 浏览器的主进程,负责协调控制其它子进程
- 第三方插件进程
- 每使用一个插件,相当于一个进程,只有在使用该插件的时候创建
- GPU进程
- 最多一个,用于3D绘制
- 浏览器渲染进程(内核)
- 默认每个Tab页面一个进程,互不影响
- 控制页面渲染,脚本执行,事件处理等
浏览器渲染进程(内核)
渲染进程当然存在多个线程啦,让我们来看看吧:
- GUI线程
- 负责渲染浏览器界面,解析HTML、CSS。
- 当页面重绘或者引发回流的时候,该线程会执行
- 该线程与JS引擎线程是互斥的,二者不可以同时进行
- JS引擎线程
- 负责js脚本程序
- 只有一个js引擎线程
- 同样,与GUI线程互斥
- 事件触发线程
- 用来控制事件循环
- 当js引擎执行代码时,会将对应任务添加到事件触发线程中
- 当对应事件触发,线程会把事件添加到任务队列队尾,等待响应
- 定时触发线程
- setInterval()和setTimeout()所在的线程
- 定时任务不是由js引擎计数的
- 异步http请求线程
- 浏览器有一个单独的线程用于处理ajax请求
GUI渲染线程和JS引擎线程互斥: 因为js时可以操作DOM的,如果在修改这些元素属性的时候同时渲染页面,那渲染前后获得的元素数据可能会不一致。所以为防止渲染出现不可预期的结果,浏览器设置了二者是互斥的。
Event Loop
什么是Event loop
-
js分为同步任务和异步任务
-
同步任务在
js引擎线程上执行,形成一个执行栈 -
事件触发线程管理一个任务队列定时触发线程本身是一个同步任务,但其中的回调函数是异步任务,所以js引擎线程会通知定时器触发线程,当定时器触发线程接收到消息后,会在等待的时间后将回调函数放入到任务队列中去- 同样,
异步http请求线程会接收到一个来自js引擎线程的通知,发送一个网络请求,当异步http请求线程接收到消息后,在请求成功后,将回调函数放入任务队列中去
-
当
执行栈中同步任务执行完毕,js引擎线程空闲,系统会自动识别任务队列,将可运行的异步任务添加到执行栈中。
用一张图来解释,可能更加清楚:
宏任务和微任务
微任务和宏任务皆为异步任务,它们都属于一个队列,主要区别在于他们的执行顺序,Event Loop的走向和取值。
什么是宏任务
我们将每次执行栈执行的代码当作一个宏任务,前文中提到过GUI渲染线程和js引擎线程,它们二者是互斥的,浏览器为了使宏任务和DOM任务有序进行,会在一个宏任务执行之后,下一个宏任务执行之前,对页面进行渲染。
宏任务有:
- 整体代码script
- setInterval()
- setTImeout()
- ...
什么是微任务
微任务可以理解为在宏任务执行后立马执行的任务,也就是说,当宏任务执行后,在渲染页面前,将所有的微任务执行完,再进行页面的渲染。
微任务有:
- 原生Promise
- process.nextTick
- ...
如何区分宏任务与微任务
-
宏任务是js宿主提供的
- 目前较为常见宿主有
浏览器和node
- 目前较为常见宿主有
-
微任务是语言标准(js本身)提供的
- javascript是由ECMA制定标准的,所以语言标准提供的就是微任务,如ES6中提供的
Promise
- javascript是由ECMA制定标准的,所以语言标准提供的就是微任务,如ES6中提供的
综上所述,setTimeout是宿主提供的,Promise是ES6提供的,所以Promise会比setTimeout定时器更早执行。
栗子
1、
console.log(1)
Promise.resolve().then(() => {
console.log(2)
})
console.log(3) //输出结果为: 1、3、2
- 因为
Promise中的then方法异步回调函数,所以2最后执行。
2、
console.log(1)
setTimeout(() => {
console.log(2)
}, 0);
new Promise((resolve, reject) => {
console.log(3)
resolve()
}).then(() => {
console.log(4)
})
console.log(5) //输出结果: 1、3、5、4、2
-
顺序执行,执行同步任务,可得出1
-
遇到
setTimeout,将其推入事件队列中,并且为宏任务 -
遇到
Promise,直接执行输出代码3,将then方法的回调函数放到微任务事件队列中 -
执行同步任务,输出5
-
第一轮实行完毕,查看
微任务,即可输出4 -
第二轮开始,先开始执行
宏任务,输出2
3、
console.log(1)
new Promise((resolve, reject) => {
console.log(2)
setTimeout(() => {
console.log(3)
}, 0);
resolve()
}).then(() => {
console.log(4)
})
setTimeout(() => {
console.log(5)
new Promise((resolve, reject) => {
console.log(6)
resolve()
}).then(() => {
console.log(7)
})
}, 0);
console.log(8) //输出结果: 1、2、8、4、3、5、6、7
- 顺序执行,执行同步任务,输出1
- 遇到
Promise,直接输出2,将setTimeout(1)推入宏任务事件队列中,并将then方法回调函数放入微任务事件队列中 - 遇到
setTimeout(2),将其推入宏任务事件队列中去 - 执行同步任务,输出8
- 第一轮完成,找到
微任务,输出4 - 第二轮,先执行
宏任务,setTimeout(1)输出3 - 查看没有
微任务,即输出宏任务,setTimeout(2)输出5 setTimeOut(2)中有Promise,直接执行输出6,并将then方法回调函数放入微任务中- 执行
微任务,输出7
总结
用一幅图来总结吧~
注:此文为本人学习过程中的笔记记录,如果有错误或者不准确的地方请大佬多多指教~