标签:https 承担 存在 电力 string 含义 最小 gpu time
几乎在每一本js相关的书籍中,都会说js是单线程的,JS是通过事件队列(Event Loop)的方式来实现异步回调的。 对很多初学JS的人来说,根本搞不清楚单线程的JS为什么拥有异步的能力,所以,我试图从进程、线程的角度来解释这个问题。
说到CPU和进程、线程,对计算机操作系统有过学习和了解的同学应该比较熟悉。
计算机的核心是CPU,它承担了所有的计算任务。
它就像一座工厂,时刻在运行。
假定工厂的电力有限,一次只能供给一个车间使用。 也就是说,一个车间开工的时候,其他车间都必须停工。 背后的含义就是,单个CPU一次只能运行一个任务。
进程就好比工厂的车间,它代表CPU所能处理的单个任务。 进程之间相互独立,任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。 CPU使用时间片轮转进度算法来实现同时运行多个进程。
从上文我们已经简单了解了CPU、进程、线程,简单汇总一下。
我们已经知道了CPU、进程、线程之间的关系,对于计算机来说,每一个应用程序都是一个进程, 而每一个应用程序都会分别有很多的功能模块,这些功能模块实际上是通过子进程来实现的。 对于这种子进程的扩展方式,我们可以称这个应用程序是多进程的。
而对于浏览器来说,浏览器就是多进程的,我在Chrome浏览器中打开了多个tab,然后打开windows控制管理器:
如上图,我们可以看到一个Chrome浏览器启动了好多个进程。
总结一下:
主进程
第三方插件进程
GPU进程
渲染进程,就是我们说的浏览器内核
那么浏览器中包含了这么多的进程,那么对于普通的前端操作来说,最重要的是什么呢?
答案是渲染进程,也就是我们常说的浏览器内核
从前文我们得知,进程和线程是一对多的关系,也就是说一个进程包含了多条线程。
而对于渲染进程来说,它当然也是多线程的了,接下来我们来看一下渲染进程包含哪些线程。
GUI渲染线程
JS引擎线程
事件触发线程
定时触发器线程
异步http请求线程
当我们了解了渲染进程包含的这些线程后,我们思考两个问题:
首先是历史原因,在创建 javascript 这门语言时,多进程多线程的架构并不流行,硬件支持并不好。
其次是因为多线程的复杂性,多线程操作需要加锁,编码的复杂性会增高。
而且,如果同时操作 DOM ,在多线程不加锁的情况下,最终会导致 DOM 渲染的结果不可预期。
这是由于 JS 是可以操作 DOM 的,如果同时修改元素属性并同时渲染界面(即 JS线程和UI线程同时运行), 那么渲染线程前后获得的元素就可能不一致了。
因此,为了防止渲染出现不可预期的结果,浏览器设定 GUI渲染线程和JS引擎线程为互斥关系, 当JS引擎线程执行时GUI渲染线程会被挂起,GUI更新则会被保存在一个队列中等待JS引擎线程空闲时立即被执行。
到了这里,终于要进入我们的主题,什么是 Event Loop
先理解一些概念:
在前端开发中我们会通过setTimeout/setInterval来指定定时任务,会通过XHR/fetch发送网络请求, 接下来简述一下setTimeout/setInterval和XHR/fetch到底做了什么事
我们知道,不管是setTimeout/setInterval和XHR/fetch代码,在这些代码执行时, 本身是同步任务,而其中的回调函数才是异步任务。
当代码执行到setTimeout/setInterval时,实际上是JS引擎线程通知定时触发器线程,间隔一个时间后,会触发一个回调事件, 而定时触发器线程在接收到这个消息后,会在等待的时间后,将回调事件放入到由事件触发线程所管理的事件队列中。
当代码执行到XHR/fetch时,实际上是JS引擎线程通知异步http请求线程,发送一个网络请求,并制定请求完成后的回调事件, 而异步http请求线程在接收到这个消息后,会在请求成功后,将回调事件放入到由事件触发线程所管理的事件队列中。
当我们的同步任务执行完,JS引擎线程会询问事件触发线程,在事件队列中是否有待执行的回调函数,如果有就会加入到执行栈中交给JS引擎线程执行
用一张图来解释:
再用代码来解释一下:
let timerCallback = function() {
console.log(‘wait one second‘);
};
let httpCallback = function() {
console.log(‘get server data success‘);
}
// 同步任务
console.log(‘hello‘);
// 同步任务
// 通知定时器线程 1s 后将 timerCallback 交由事件触发线程处理
// 1s 后事件触发线程将 timerCallback 加入到事件队列中
setTimeout(timerCallback,1000);
// 同步任务
// 通知异步http请求线程发送网络请求,请求成功后将 httpCallback 交由事件触发线程处理
// 请求成功后事件触发线程将 httpCallback 加入到事件队列中
$.get(‘www.xxxx.com‘,httpCallback);
// 同步任务
console.log(‘world‘);
//...
// 所有同步任务执行完后
// 询问事件触发线程在事件事件队列中是否有需要执行的回调函数
// 如果没有,一直询问,直到有为止
// 如果有,将回调事件加入执行栈中,开始执行回调代码
总结一下:
资源搜索网站大全 https://www.renrenfan.com.cn 广州VI设计公司https://www.houdianzi.com
当我们基本了解了什么是执行栈,什么是事件队列之后,我们深入了解一下事件循环中宏任务、微任务
我们可以将每次执行栈执行的代码当做是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行), 每一个宏任务会从头到尾执行完毕,不会执行其他。
我们前文提到过JS引擎线程和GUI渲染线程是互斥的关系,浏览器为了能够使宏任务和DOM任务有序的进行,会在一个宏任务执行结果后,在下一个宏任务执行前,GUI渲染线程开始工作,对页面进行渲染。
// 宏任务-->渲染-->宏任务-->渲染-->渲染...
主代码块,setTimeout,setInterval等,都属于宏任务
第一个例子:
document.body.style = ‘background:black‘;
document.body.style = ‘background:red‘;
document.body.style = ‘background:blue‘;
document.body.style = ‘background:grey‘;
我们可以将这段代码放到浏览器的控制台执行以下,看一下效果:
我们会看到的结果是,页面背景会在瞬间变成灰色,以上代码属于同一次宏任务,所以全部执行完才触发页面渲染,渲染时GUI线程会将所有UI改动优化合并,所以视觉效果上,只会看到页面变成灰色。
第二个例子:
document.body.style = ‘background:blue‘;
setTimeout(function(){
document.body.style = ‘background:black‘
},0)
执行一下,再看效果:
我会看到,页面先显示成蓝色背景,然后瞬间变成了黑色背景,这是因为以上代码属于两次宏任务,第一次宏任务执行的代码是将背景变成蓝色,然后触发渲染,将页面变成蓝色,再触发第二次宏任务将背景变成黑色。
我们已经知道宏任务结束后,会执行渲染,然后执行下一个宏任务, 而微任务可以理解成在当前宏任务执行后立即执行的任务。
也就是说,当宏任务执行完,会在渲染前,将执行期间所产生的所有微任务都执行完。
Promise,process.nextTick等,属于微任务。
第一个例子:
document.body.style = ‘background:blue‘
console.log(1);
Promise.resolve().then(()=>{
console.log(2);
document.body.style = ‘background:black‘
});
console.log(3);
执行一下,再看效果:
控制台输出 1 3 2 , 是因为 promise 对象的 then 方法的回调函数是异步执行,所以 2 最后输出
页面的背景色直接变成黑色,没有经过蓝色的阶段,是因为,我们在宏任务中将背景设置为蓝色,但在进行渲染前执行了微任务, 在微任务中将背景变成了黑色,然后才执行的渲染。
第二个例子:
setTimeout(() => {
console.log(1)
Promise.resolve(3).then(data => console.log(data))
}, 0)
setTimeout(() => {
console.log(2)
}, 0)
// print : 1 3 2
上面代码共包含两个 setTimeout ,也就是说除主代码块外,共有两个宏任务, 其中第一个宏任务执行中,输出 1 ,并且创建了微任务队列,所以在下一个宏任务队列执行前, 先执行微任务,在微任务执行中,输出 3 ,微任务执行后,执行下一次宏任务,执行中输出 2
标签:https 承担 存在 电力 string 含义 最小 gpu time
原文地址:https://www.cnblogs.com/xiaonian8/p/14113487.html