首页 > 编程语言 >异步编程之事件循环机制

异步编程之事件循环机制

时间:2023-04-04 20:37:40浏览次数:46  
标签:Node 异步 单线程 编程 任务 循环 事件 执行


JavaScript 是一门单线程语言,我们可以通过异步编程的方式来实现实现类似于多线程语言的并发操作。

本文着重讲解通过事件循环机制来实现多个异步操作的有序执行、并发执行;通过事件队列实现同级多个并发操作的先后执行顺序,通过微任务和宏任务的概念来讲解不同阶段任务执行的先后顺序,最后通过将浏览器和 Node 下的事件循环机制进行对比,对比其事件循环机制的不同之处,以及在 Node 端通过libuv引擎来实现多个异步任务的并发执行。

一、前言

我们知道JavaScript 是一门单线程语言,对于大多数人而言,单线程最大的好处是不用像多线程那样处处在意状态的同步问题,这里没有死锁的存在,也没有像多线程之间来回切换带来性能上的开销。同样,单线程也存在自身的弱点,主要表现在以下几个方面:

  1. 无法利用多核cpu,一个简单的例子,在一个位置从同一台服务器拉取不同的资源,如果采用单线程同步的方式去拉取,代码大致如下:
getData(‘from_db’),//耗时为M,
getData(‘from_db_api’),//耗时为N,
如果采用同步单线程的方式总共耗时为:M+N
  1. js代码错误或者耗时过长会阻塞后面代码的执行,例如页面在进行dom渲染时,如果页面的js代码报错会引起整个页面白屏的现象。
  2. 大量计算占用CPU导致无法继续调用异步I/O。
    后来HTML5定制了Web Workers能够创建多线程来进行计算,但是使用Web Workers技术开的多线程有着诸多的限制,例如:所有新线程都受主线程的完全控制,不能独立执行。这意味着这些“线程” 实际上应属于主线程的子线程。另外,这些子线程并没有执行I/O操作的权限,只能为主线程分担一些简单的计算任务。所以严格来讲这些线程并没有完整的功能,也因此这项技术并非改变了 JavaScript 语言的单线程本质。

    所以我们可以预见,未来的 JavaScript 依然会是一门单线程语言,因此JavaScript采用异步编程方式实现程序“非阻塞”的特点,那么我们如何实现这一特征了,答案就是我们今天要讲的——event loop(事件循环)。

二、浏览器下的事件循环机制

1、执行栈

JavaScript变量主要存储在堆和栈两个位置,其中,堆里主要存储对象,栈主要存储基本类型的变量以及指针变量。当我们调用一个方法时,JS 会生成一个与这个方法对应的执行环境,又叫执行上下文,当一系列方法被调用时,由于我们的js是单线程的,所以这些方法会被单独排在一个地方,这个地方叫做执行栈。
当一个脚本第一次执行的时候,JS  引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,然后从头开始执行。如果当前执行的是一个方法,那么 JS 会向执行栈中添加这个方法的执行环境,然后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后,JS 会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。这个过程反复进行,直到执行栈中的代码全部执行完毕。

2、事件队列

以上说的都是 JS 同步代码的执行,那么当程序执行异步代码后会如何进行呢?我们前面提到过 JS 最大的特点是非阻塞,下面我们说一下实现这一点的关键在于这项机制——事件队列。

当js引擎遇到一个异步事件后不会一直等待返回结果,这个事件会先挂起,继续执行执行栈中的其他任务,直到这个异步事件的结果返回,JS 引擎会将这个事件放入与当前执行栈不同的一个队列中,我们称之为事件队列。

被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码...,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。


异步编程之事件循环机制_单线程


 

 

3、微任务和宏任务

关于微任务和宏任务我们可以用一张图来说明:


异步编程之事件循环机制_javascript_02


 

在一个事件循环中,异步事件返回结果后会被放到一个任务队列中。然而,根据这个异步事件的类型,这个事件实际上会被对应的宏任务队列或者微任务队列中去。并且在当前执行栈为空的时候,主线程会 查看微任务队列是否有事件存在。如果不存在,那么再去宏任务队列中取出一个事件并把对应的回调加入当前执行栈;如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈...如此反复,进入循环。

宏任务主要包含:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)

微任务主要包含:Promise、MutaionObserver、process.nextTick(Node.js 环境)

三、Node环境下的事件循环模型

与浏览器有何异同?

在 Node 中,事件循环表现出的状态与浏览器中大致相同。不同的是 Node  中有一套自己的模型。Node  中事件循环的实现是依靠的libuv引擎。我们知道 Node  选择Chrome V8引擎作为js解释器,V8引擎将js代码分析后去调用对应的Node   api,而这些api最后则由libuv引擎驱动,执行对应的任务,并把不同的事件放在不同的队列中等待主线程执行。因此实际上 Node  中的事件循环存在于libuv引擎中。


异步编程之事件循环机制_单线程_03


 

从上面这个模型中,我们可以大致分析出 Node  中的事件循环的顺序:

外部输入数据-->轮询阶段(poll)-->检查阶段(check)-->关闭事件回调阶段(close callback)-->定时器检测阶段(timer)-->I/O事件回调阶段(I/O callbacks)-->闲置阶段(idle, prepare)-->轮询阶段...

以上各阶段的名称是根据我个人理解的翻译,为了避免错误和歧义,下面解释的时候会用英文来表示这些阶段。这些阶段大致的功能如下:

timers: 这个阶段执行定时器队列中的回调如 setTimeout() 和 setInterval()。

  • I/O callbacks: 这个阶段执行几乎所有的回调。但是不包括close事件,定时器和setImmediate()的回调。
  • idle, prepare: 这个阶段仅在内部使用,可以不必理会。
  • poll: 等待新的I/O事件,node在一些特殊情况下会阻塞在这里。
  • check: setImmediate()的回调会在这个阶段执行。
  • close callbacks: 例如socket.on('close', ...)这种close事件的回调。

四、小结

JavaScript事件循环是非常重要的一个基础概念,我们可以通过这种机制实现异步编程,解决JavaScript同步单线程无法实现并发操作的问题,可以使我们对一段异步代码的执行顺序有一个清晰的认识,从而减少代码运行的不确定性。合理的使用各种延迟事件的方法,有助于代码更好的按照其优先级去执行。

作者:Liu Gang

标签:Node,异步,单线程,编程,任务,循环,事件,执行
From: https://blog.51cto.com/u_14291117/6169493

相关文章

  • Python——异步编程案例
    摘要主要是讲解Python中的异步编程的下的实际的案例案例:异步操作redis案例:异步操作MySQL案例:FastAPl框架异步案例:异步爬虫课程总结......
  • python——异步编程代码实战
    摘要主要介绍python中相关的异步编程的原理和是代码的实战协程实现协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文切换技术。简而言之,其实就是通过一个线程实现代码块相互切换执行。协程不是计算机提供,程序员人为创造。协程的优点:在一个线程中如果遇到IO等待时间,线程不......
  • 并发编程——JUC并发大厂面试问题
    摘要现如今,不管是应届毕业生还是工作了三五年之内的工程师,在面试招聘的时候JUC并发编程的是必须掌握的一个技能,否者你将会被面试官玩弄。本博文将整理有关于的JUC的大厂面试问题和答案。帮助大家在面试过程中能够回答面试官问题的一二。同时本人也总结相关的面试问题的在相关文档中......
  • SQL Server 数据库T-SQL编程
    1、T-SQL编程通过SQL语句来完成业务的处理,执行编写好的sql语句,就可以完成业务处理。2、局部变量SQLserver中变量分为:局部变量和全局变量。全局变量,在全局可用,系统自定义,用户不可以定义全局变量,用不不可以修改全局变量,全局变量以“@@”开头局部变量就是一个能够拥有......
  • 系统化学习前端之JavaScript(ES6:异步编程)
    前言JavaScript异步编程这块东西比较多,涉及到宏任务和微任务,所以单开一个篇幅梳理一下。同步和异步同步和异步是一种宏观概念,具体表现在JavaScript中,是同步任务和异步任务,即同步函数和异步函数。同步同步指函数在JavaScript同步执行。同步函数执行过程:A函数进入函数调......
  • 学了这么久的高并发编程,连Java中的并发原子类都不知道?
    摘要:保证线程安全是Java并发编程必须要解决的重要问题,本文和大家聊聊Java中的并发原子类,看它如何确保多线程的数据一致性。本文分享自华为云社区《学了这么久的高并发编程,连Java中的并发原子类都不知道?这也太Low了吧》,作者:冰河。今天我们一起来聊聊Java中的并发原子类。在 j......
  • lambda中的forEach如何跳出循环
    lambda中的forEach如何跳出循环    前提   在Java8中的forEach()中,"break"或"continue"是不被允许使用的,而return的意思也不是原来return代表的含义了。forEach(),说到底是一个方法,而不是循环体,结束一个方法的执行自然是用return。    1. 在Java8中直接......
  • Apache DB Utils教程_编程入门自学教程_菜鸟教程-免费教程分享
    教程简介ApacheCommonsDBUtils入门教程-从基本到高级概念的简单简单步骤熟悉ApacheCommonsDBUtils,其中包括概述,环境设置,第一个应用程序,基本CRUD示例,创建,读取,更新,删除查询,DBUtils对象,QueryRunner,AsyncQueryRunner,ResultSetHandler,BeanHandler,ArrayListHandler,BeanListHandle......
  • 《Python编程快速上手—让繁琐工作自动化》实践项目答案:第六章
    实践项目表格打印编写一个名为printTabel()的函数,它接受字符串的列表的列表,将它显示在组织良好的表格中,每列右对齐,假定所有内层列表都包含同样数目的字符串,例如:你的printTable()函数将打印出:点击查看代码tableData=[['apples','oranges','cherries','banana'],......
  • 函数式编程杂谈
    vivo互联网技术微信公众号作者:张文博比起命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断演进,逐层推导出复杂的运算。本文通过函数式编程的一些趣味用法来阐述学习函数式编程的奇妙之处。一、编程范式综述编程是为了解决问......