当前位置: 首页 > news >正文

node 基础与 Event Loop

node 的特点

  • 主线程是单线程
  • 异步非阻塞(非阻塞I/O)
  • 事件驱动

单线程

  • 进程和线程

进程是操作系统分配资源和调度任务的基本单位,线程是建立在进程上的一次程序运行单位,一个进程上可以有多个线程。

  • 什么是单线程?

一个进程中只有一个线程,程序顺序执行,前面的执行完成后才会执行后面的程序。

有点儿像点菜一样, 顾客来了。服务员接单,服务员接完单后马上告诉厨房去做菜吧,此时服务员不会等待菜做好而是马上会被释放出来继续服务下一个客户。 一直都是一个服务员在工作,等菜做好了,服务员在回去取菜给客户吃。

单线程特点是节约内存,并且不需要再切换执行上下文,而且单线程不需要考虑锁的问题。

如下图

异步非阻塞

  • 同步和异步

同步和异步关注的是消息通知机制,指代的是被调用方

同步就是发出调用后,没有得到结果前,该调用不返回,一旦调用返回,就得到返回值

当一个异步过程调用发出后,调用者不会立刻得到结果,而是调用发出后,被调用者通过状态、通知或者回调函数处理这个调用。

  • 阻塞和非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息、返回值)的状态,针对的是调用者

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果后才会返回

非阻塞调用指在不能立刻得到结果前,该调用不会阻塞当前线程

a>  同步阻塞
    调用者:我喜欢你 
    被调用者:我也喜欢你
    
b>  异步阻塞
    调用者:  我喜欢你
    被调用者:我和我妈商量下。回头回复你
    调用者:  不挂断电话,我等你回复
    
c>  异步非阻塞
    调用者: 我喜欢你
    被调用者: 我和我妈商量下。回头回复你
    调用者:  挂断电话,不等你了。联系了另外一个妹子
    
d>  同步非阻塞
    调用者:给 A 打电话 我喜欢你
    被调用者(A):正准备回复
    调用者:不挂断电话 A 转身又给 B 打电话 我喜欢你
复制代码
  • I/O 操作

    • 访问服务器的静态资源

    • 读取数据,读取文件

node 在处理高并发, I/O密集场景有明显优势。高并发是指在同一时间并发访问服务器,I/O密集知道是文件操作、网络操作、数据库。相对的有 CPU 密集,CPU 密集值的是逻辑处理运算、压缩、解压、加密、解密等。 可是菜做好了,如何告诉服务员呢? ==回调==

事件驱动

谈谈浏览器中的Event Loop

  • 渲染引擎

渲染引擎内部是多线程的,包括 UI 线程和 JS 线程。注意 UI 线程和 JS 线程是互斥的,因为 JS 运行结果会影响到 UI 线程的结果。 UI 更新会被保存在任务队列中等 JS 线程空闲时候立即被执行。

  • JS 单线程(主线程)

JS 在最初为什么被设计成了单线程,而不是多线程呢?如果多个线程同时操作 DOM 那岂不是会很混乱?

  • 其他线程

    • 浏览器事件触发线程(用来控制事件循环、存放seTimeout、浏览器事件、ajax回调)
    • 定时触发器线程(setTimeout)
    • 异步 HTTP 请求线程(ajax请求线程)

浏览器中的 Event Loop

来个经典的图

  • 所有同步任务都在主线程上执行,形成一个执行栈
  • 主线程之外,还存在一个任务队列,只要异步任务有了运行结果,就在任务队列中放一个事件
  • 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,将任务队列中的事件放到执行栈中依次执行
  • 主线程从任务队列中读取事件,这个过程是循环不断的

栈内存 / 堆内存

javascript 中的变量分为基本类型和引用类型。基本类型是保存在栈内存中,引用类型则指的是保存在堆内存中的对象

任务队列

  • 任务队列 先进先出

宏观任务(MacroTask):

setTimeout
setInterval
setImmediate(只兼容IE)
MessageChannel
requestAnimationFrame
I/O
UI rendering 
复制代码

微观任务(MicroTask):

process.nextTick
Promise
Object.observe(已废弃)
MutationObserver
复制代码
  • 栈 先进后出

比如: 函数的执行栈,作用域的释放顺序。

放进去的顺序: 全局作用域 <= one <= two <= three

函数 three 没有执行完,函数 one 是不会被释放的。函数销毁的顺序则是 three => two => one => 全局

function one () {
  let a = 1;
  two();
  function two() {
    console.log(a);
    let b = 2;
    function three () {
      debugger;
      console.log(b);
    }
    three();
  }
}

one();
复制代码

断点调试下如下:进入的顺序和出去的顺序是相反的。

例子-1:

// 栈中的代码执行完毕后,会调用队列中的代码,此过程不挺的循环
// 当 1000 毫秒到达的时候 setTimeout 才会被放到任务队列里去
console.log(1);
setTimeout(() => {
  console.log(2);
}, 1000)

setTimeout(() => {
  console.log(3);
}, 500)

// 1 3 2
复制代码

例子-2:

console.log(1);
setTimeout(() => {
  console.log(2);
}, 1000)
while(true) {}
setTimeout(() => {
  console.log(3);
}, 500)
复制代码

此时不会输出 2 和 3。是因为 while 是个死循环。当时间到达时,要看栈中是否已经执行完了,如果没有执行完,就不会调用队列中的内容

例子-3:

console.log('global')
for (var i = 1;i <= 5;i ++) {
  setTimeout(function() {
    console.log('setTimeout1:', i)
  },i*1000)
  console.log(i)
}

new Promise(function (resolve) {
  console.log('promise1')
  resolve()
 }).then(function () {
  console.log('then1')
})

setTimeout(function () {
  console.log('timeout2')
  new Promise(function (resolve) {
    console.log('timeout2_promise')
    resolve()
  }).then(function () {
    console.log('timeout2_then')
  })
}, 1000)
// 输出结果
// global
// 1 
// 2
// 3
// 4
// 5
// promise1
// then1
// setTimeout1: 6
// timeout2
// timeout2_pormise 
// timeout2_then 
// setTimeout1: 6 
// setTimeout1: 6 
// setTimeout1: 6 
// setTimeout1: 6
// setTimeout1: 6 

复制代码

先执行主线程中的任务输出 global 和 for 循环中的 i。setTimeout 属于宏观任务,时间到了会放到宏任务队列中,setTimeout1 会根据 i * 1000 依次放入到宏任务队列中。Promise 构造函数中的执行器属于同步任务,会先输出 promise1, 调用 resolve 后改变了 Promise 状态,调用 then 方法会将任务放入微任务队列中。此时 setTimeout2 时间到会被放入宏任务队列中。timeout2_promise 也会根据promise1的执行过程进入到微任务队列。

每次 Event Loop 触发执行的过程是:

A> 执行主线程中的任务,调用栈为空

B> 取出==所有== micro-task 任务队列 => 执行

C> 取出==一个== macro-task 任务 => 执行

D> 取出==所有== micro-task 任务队列 => 执行

E> 重复 C 和 D

node 系统中的 Event Loop

  • js 代码会交给 V8 引擎进行处理
  • 代码中用到的 node api 会交给 libuv 库处理
  • libuv 通过阻塞 i/o 和多线程实现异步 io
  • 通过事件驱动方式,将结果放到事件队列中,最终交给我的应用

相关文章:

  • 下一个游戏新风口已来?小游戏或成2018年最大游戏黑马
  • NG
  • Android深度定制化TabLayout:圆角,渐变色,背景边框,基于Android原生TabLayout
  • LEARN SWIFT
  • 浅谈JavaScript中的继承
  • 一份游戏开发学习路线
  • 如何利用snmp协议发现大型复杂环境的网络拓扑(建议开发自动化工具的朋友可以看一下)...
  • jenkins指定具体项目具体分支进行构建部署
  • 教你一步步composer安装Magento2.3
  • 真正能支撑高并发以及高可用的复杂系统中的缓存架构有哪些东西?
  • 配置嵌入式Servlet容器
  • Spring Boot学习记4
  • 小R的烦恼 BZOJ3280
  • 捋一捋PHP第三方微信登录
  • JDK动态代理源码解析
  • 「面试题」如何实现一个圣杯布局?
  • ABAP的include关键字,Java的import, C的include和C4C ABSL 的import比较
  • echarts花样作死的坑
  • go语言学习初探(一)
  • Intervention/image 图片处理扩展包的安装和使用
  • Java面向对象及其三大特征
  • mockjs让前端开发独立于后端
  • php面试题 汇集2
  • React+TypeScript入门
  • Redis提升并发能力 | 从0开始构建SpringCloud微服务(2)
  • Vue2.0 实现互斥
  • 不用申请服务号就可以开发微信支付/支付宝/QQ钱包支付!附:直接可用的代码+demo...
  • 后端_MYSQL
  • 手写一个CommonJS打包工具(一)
  • 我看到的前端
  • 小程序 setData 学问多
  • MiKTeX could not find the script engine ‘perl.exe‘ which is required to execute ‘latexmk‘.
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • #基础#使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件
  • (ros//EnvironmentVariables)ros环境变量
  • (保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示
  • (二)丶RabbitMQ的六大核心
  • (附源码)spring boot北京冬奥会志愿者报名系统 毕业设计 150947
  • (附源码)springboot青少年公共卫生教育平台 毕业设计 643214
  • (九十四)函数和二维数组
  • (六)什么是Vite——热更新时vite、webpack做了什么
  • (每日持续更新)jdk api之StringBufferInputStream基础、应用、实战
  • (推荐)叮当——中文语音对话机器人
  • (转)Linux整合apache和tomcat构建Web服务器
  • (转)Sublime Text3配置Lua运行环境
  • * CIL library *(* CIL module *) : error LNK2005: _DllMain@12 already defined in mfcs120u.lib(dllmodu
  • ../depcomp: line 571: exec: g++: not found
  • .bat批处理出现中文乱码的情况
  • .form文件_一篇文章学会文件上传
  • .NET Core 控制台程序读 appsettings.json 、注依赖、配日志、设 IOptions
  • .Net Remoting(分离服务程序实现) - Part.3
  • .NET开源的一个小而快并且功能强大的 Windows 动态桌面软件 - DreamScene2
  • [ vulhub漏洞复现篇 ] struts2远程代码执行漏洞 S2-005 (CVE-2010-1870)
  • [ASP.NET MVC]Ajax与CustomErrors的尴尬
  • [C#]winform部署yolov5-onnx模型