资讯专栏INFORMATION COLUMN

JavaScript:并发模型与Event Loop

DevWiki / 3283人阅读

摘要:需要注意的是,定时器只是将事件插入了任务队列,必须等到当前代码执行栈执行完,主线程才会去执行它指定的回调函数。如果当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在指定的时间执行。这也引申出的并发模型。

一、JavaScript的单线程

众所周知,JavaScript的一大特点就是单线程,但是我们有没有思考过它为什么不能是多线程的?

我们假定JavaScript有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以为了避免这种复杂性,从一诞生,JavaScript就是单线程。

尽管HTML5提出Web Worker,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,并没有改变JavaScript单线程的本质。

二、定时器

定时器主要是setTimeout()和setInterval()这两个函数,这也是平时编程时候用到最多的。

console.log(1);
setTimeout(function() {
    console.log(2);
},5000);
console.log(3);

上面代码的执行结果是1,3,2。但如果将setTimeout()的第二个参数设为0,就表示当前代码执行完以后,立即执行(0毫秒延迟)指定的回调函数。setTimeout(fn,0)的含义是,它在任务队列的尾部添加一个事件,在主线程最早得到空闲时去执行,也就是说,尽可能早得执行。

需要注意的是,定时器只是将事件插入了任务队列,必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。如果当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。这也引申出JavaScript的并发模型

三、并发模型

我们先看一下理论上的并发模型:

栈(stack):函数调用会形成了一个堆栈帧
堆(heap):对象被分配在一个堆中,一个用以表示一个内存中大的未被组织的区域
队列(queue):运行时包含的一个待处理的消息队列。当栈为空时,则从队列中取出一个消息进行处理。这个处理过程包含了调用与这个消息相关联的函数(以及因而创建了一个初始堆栈帧)

四、Event Loop

针对上面的并发模型和JavaScript的同步异步运行机制,我们可以看到整个流程大致是这样的:

1.所有同步任务都在主线程上执行,形成一个执行栈(并发模型的stack)。
2.主线程之外,还存在一个任务队列(并发模型的queue)。只要异步任务有了运行结果,就在任务队列中放置一个事件。
3.一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件和那些对应的异步任务,于是等待结束状态,进入执行栈,开始执行。
4.主线程不断重复上面的第三步。

这个过程是循环不断的,所以这种运行机制又称为Event Loop(事件循环)。放一张大神演讲时的图片来更好地理解Event Loop:

我们可以看到,主线程运行的时候,产生(heap)和(stack),中的代码调用各种外部API,它们在任务队列中加入各种事件(click,load,done)。只要中的代码执行完毕,主线程就会去读取任务队列,依次执行那些事件所对应的回调函数。

五、Macrotask 和 Microtask

这是一个比较冷门的知识,在并发模型中队列又可以分为Macrotask 和 Microtask,它们都属于异步任务。先来看一个例子:

console.log("script start");

setTimeout(function() {
    console.log("setTimeout");
}, 0);

Promise.resolve().then(function() {
    console.log("promise1");

    setTimeout(function() {
        console.log("setTimeout in microtask");
    }, 0);
}).then(function() {
    console.log("promise2");
});

console.log("script end");

输出:

script start
script end
promise1
promise2
setTimeout
setTimeout in microtask

Macrotask 和 Microtask有什么区别呢?

Macrotasks:setTimeout, setInterval, setImmediate, I/O, UI rendering

Microtask:process.nextTick, Promises, Object.observe(废弃), MutationObserver

它们的执行过程如下:

JavaScript引擎首先从macrotask queue中取出第一个任务
执行完毕后,将microtask queue中的所有任务取出,按顺序全部执行
然后再从macrotask queue中取下一个
执行完毕后,再次将microtask queue中的全部取出
循环往复,直到两个queue中的任务都取完

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/88472.html

相关文章

  • Javascript】探究javascript中的堆/栈/任务队列并发模型 event loop

    摘要:而函数调用结束返回时,运行时会将栈顶的调用结构弹出。并发模型与引擎是单线程的,它的并发模型基于事件循环当线程中的同步任务执行完,执行栈为空时,则从任务队列中取出异步任务进行处理。在当前的微任务没有执行完成时,是不会执行下一个宏任务的。 堆/栈/队列 在javascript中,存在调用栈 (call stack)和内存堆(memory heap) ,程序中函数依次进入栈中等待执行,若执行...

    desdik 评论0 收藏0
  • 第五天 JavaScript单线程详解

    摘要:若以多线程的方式操作这些,则可能出现操作的冲突。另外,因为是单线程的,在某一时刻内只能执行特定的一个任务,并且会阻塞其它任务执行。浏览器事件触发线程事件触发线程,当一个事件被触发时该线程会把事件添加到任务队列的队尾,等待引擎的处理。 首先,说下为什么 JavaScript 是单线程? 总所周知,JavaScript是以单线程的方式运行的。说到线程就自然联想到进程。那它们有什么联系呢? ...

    caiyongji 评论0 收藏0
  • 初窥JavaScript事件机制的实现(一)—— Node.js事件驱动实现概览

    摘要:如果当前没有事件也没有定时器事件,则返回。相关资料关于的架构及设计思路的事件讨论了使用线程池异步运行代码。下一篇初窥事件机制的实现二中定时器的实现 在浏览器中,事件作为一个极为重要的机制,给予JavaScript响应用户操作与DOM变化的能力;在Node.js中,事件驱动模型则是其高并发能力的基础。 学习JavaScript也需要了解它的运行平台,为了更好的理解JavaScript的事...

    lavor 评论0 收藏0
  • 细说JavaScript单线程的一些事

    摘要:标签单线程首发地址码农网细说单线程的一些事最近被同学问道单线程的一些事,我竟回答不上。若以多线程的方式操作这些,则可能出现操作的冲突。另外,因为是单线程的,在某一时刻内只能执行特定的一个任务,并且会阻塞其它任务执行。 标签: JavaScript 单线程 首发地址:码农网《细说JavaScript单线程的一些事》 最近被同学问道 JavaScript 单线程的一些事,我竟回答不上。好...

    sarva 评论0 收藏0
  • 细说JavaScript单线程的一些事

    摘要:标签单线程首发地址码农网细说单线程的一些事最近被同学问道单线程的一些事,我竟回答不上。若以多线程的方式操作这些,则可能出现操作的冲突。另外,因为是单线程的,在某一时刻内只能执行特定的一个任务,并且会阻塞其它任务执行。 标签: JavaScript 单线程 首发地址:码农网《细说JavaScript单线程的一些事》 最近被同学问道 JavaScript 单线程的一些事,我竟回答不上。好...

    coolpail 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<