摘要:引擎线程,也称为内核,负责处理脚本程序,例如引擎。异步请求线程,也就是发出请求后,接收响应检测状态变更等都是这个线程管理的。为了解决这个问题,提出标准,允许脚本创建多个线程,但是子线程完全受主线程控制,且不得操作。
本文主要参阅了以下两篇文章,对JS的Event Loop运行机制基础知识进行了整理。
从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理
JavaScript 运行机制详解:再谈Event Loop
大家都知道JavaScript是单线程的,这就引申出一个问题,进程与线程是什么,他们的区别是什么?
先给出进程和线程的定义:
进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
用工厂和工人的例子来形象阐述:
- 进程是一个工厂,工厂有它的独立资源 -> 系统分配的内存(独立的一块内存) - 工厂之间相互独立 -> 进程之间相互独立 - 线程是工厂中的工人,多个工人协作完成任务 -> 多个线程在进程中协作完成任务 - 工厂内有一个或多个工人 -> 一个进程由一个或多个线程组成 - 工人之间共享工厂的资源 -> 同一进程下的各个线程之间共享进程的内存空间(包括代码段、数据集、堆等)
补充:
我们所说的单线程和多线程,是指一个进程内是单一线程还是多线程。
进程间的通信方式包括: 管道pipe、 命名管道FIFO、消息队列MessageQueue、共享存储SharedMemory、信号量Semaphore、套接字Socket、信号。
浏览器是多进程的关于浏览器进程问题可以简单基础三点:
浏览器是多进程的。
浏览器之所以能够运行,是因为系统给它的进程分配了资源(cpu、内存)。
简单点理解,每打开一个Tab页,就相当于创建了一个独立的浏览器进程。
平时 coding 接触到最多的一个浏览器进程是浏览器渲染进程(浏览器内核),它管理着页面渲染。脚本执行,事件处理等。要同时处理这么多事情,渲染进程显然是多线程的,它主要包括以下5个常驻线程:
GUI渲染线程,负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
JS引擎线程,也称为JS内核,负责处理Javascript脚本程序,(例如V8引擎)。
事件触发线程,用来控制事件循环(可以理解为,JS引擎线程自己都忙不过来,需要浏览器另开线程协助)。
定时触发器线程,浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确),JS中常用的setInterval和setTimeout就归这个线程管理。
异步http请求线程,也就是ajax发出http请求后,接收响应、检测状态变更等都是这个线程管理的。
我们常说的JavaScript是单线程的,其实就是说的JS引擎是单线程的,它仅仅是浏览器渲染进程种的一个线程。为什么呢?因为JavaScript的主要作用是与用户互动,以及操作DOM,如果JavaScript有两个线程,一个线程对一个DOM节点执行 A 操作,另一个线程这个DOM节点执行 B 操作,那么就会起冲突,所以JavaScript在前端的应用就注定了它是单线程的。
然而JavaScript的单线程特性就注定我们不用它去完成密集的 cpu 运算,因为密集 cpu 运算耗时过长,阻塞页面渲染。为了解决这个问题,HTML5提出 Web Worker 标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。
Event LoopJavaScript 是单线程的带来的问题是:所有任务都必须同步执行,问题就出现了,很多 I/O 过程是非常耗时的(如http 请求数据),如果要等到 I/O 过程结束再执行后续任务,就会出现页面的卡顿、cpu 的闲置。于是异步的任务就出现了,异步任务是指挂起处于等待中的任务,继续执行同步任务,等到结果返回再去继续执行被挂起的任务。于是,JavaScript 的任务可以分为同步任务和异步任务。下面就引出 Event Loop 机制:
所有同步任务都在主线程上执行,形成一个执行栈
主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到执行栈中,开始执行。
如上图所示,执行栈中的代码会调用一个异步的API,它们会在任务队列中添加各种事件(或者说回调函数),另外用户的操作如click、mousedown等都会在任务队列中添加事件。只要执行栈中的代码执行完毕,主线程就会去读取任务队列,将可执行的回调函数放到执行栈中执行。
总结一下:
执行栈执行完毕 -> 主线程读取任务队列,并执行回调函数 -> 执行栈执行完毕 -> 主线程读取任务队列,并执行回调函数 ...
这个过程一直循环下去,所以就叫事件循环(Event Loop)。
setTimeout 和 setInterval前面提到了浏览器的定时触发器线程,它的主要作用就是计时,setTimeout 和 setInterval 就由它来控制,原理就是到达设置时间后,往任务队列中添加这两个函数中指定的回调函数。
setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。但是需要注意的是,实际是计时结束后定时触发器线程才会将回调函数放到任务队列中去,此时任务队列中这个回调之前可能已经有一些事件待处理,并且一定要执行栈的任务执行完后才会开始执行任务队列中的任务,所以 setTimeout() 中回调开始执行的时间是:执行栈执行时间 + 任务队列前方回调执行时间 + 延迟时间
setInterval() 方法可按照指定的时间间隔来周期性调用函数或计算表达式。它的问题在于:每次都精确的隔一段时间将一个回调放到任务队列中,并没有考虑到内部回调函数执行所需时间,这就会导致两种问题:
回调函数执行需要时间,两个函数执行的时间间隔会小于设定值;
如果回调函数执行时间大于设定间隔,就会出现上一个加入任务队列中的回调还没执行完,下一个回调就被加入任务队列了,就会出现累计效应,即后面的回调会连续执行。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/96459.html
摘要:规范中定义了浏览器何时进行渲染更新,了解它有助于性能优化。结合一些资料,对上边规范给出一些理解有误请指正每个线程都有自己的。列为,列为,列为。我们都知道是单线程,渲染计算和脚本运行共用同一线程网络请求会有其他线程,导致脚本运行会阻塞渲染。 本文转自blog 转载请注明出处 异步的思考 event loops隐藏得比较深,很多人对它很陌生。但提起异步,相信每个人都知道。异步背后的靠山就是...
摘要:但是导致了很明显的性能问题。上述两个例子其实是在这个中找到的,第一个使用的版本是,这个版本的实现是采用了,而后因为的里的有,于是尤雨溪更改了实现,换成了,也就是后一个所使用的。后来尤雨溪了解到是将回调放入的队列。 结论 对于event loop 可以抽象成一段简单的代码表示 for (macroTask of macroTaskQueue) { // 1. Handle cur...
摘要:但是导致了很明显的性能问题。上述两个例子其实是在这个中找到的,第一个使用的版本是,这个版本的实现是采用了,而后因为的里的有,于是尤雨溪更改了实现,换成了,也就是后一个所使用的。后来尤雨溪了解到是将回调放入的队列。 结论 对于event loop 可以抽象成一段简单的代码表示 for (macroTask of macroTaskQueue) { // 1. Handle cur...
摘要:事件循环了解了在引擎中是如何工作了之后,来看下如何使用异步回调函数来避免代码。从回调函数被放入后秒钟,把移到中。由于事件循环持续地监测调用栈是否已空,此时它一注意到调用栈空了,就调用并创建一个新的调用栈。 听多了JavaScript单线程,异步,V8,便会很想去知道JavaScript是如何利用单线程来实现所谓的异步的。我参考了一些文章,了解到一个很重要的词汇:事件循环(Event L...
摘要:主线程在任务队列中读取事件,这个过程是循环不断地,所以这种运行机制叫做事件循环是在执行栈同步代码结束之后,下一次任务队列执行之前。 单线程 javascript为什么是单线程语言,原因在于如果是多线程,当一个线程对DOM节点做添加内容操作的时候,另一个线程要删除这个DOM节点,这个时候,浏览器应该怎么选择,这就造成了混乱,为了解决这类问题,在一开始的时候,javascript就采用单线...
阅读 2418·2021-11-25 09:43
阅读 1196·2021-09-07 10:16
阅读 2604·2021-08-20 09:38
阅读 2939·2019-08-30 15:55
阅读 1453·2019-08-30 13:21
阅读 886·2019-08-29 15:37
阅读 1437·2019-08-27 10:56
阅读 2094·2019-08-26 13:45