美文网首页
前端进阶全栈-Node的异步IO

前端进阶全栈-Node的异步IO

作者: stevekeol | 来源:发表于2020-01-11 18:56 被阅读0次

本文力图详尽解释node的异步IO:

  • 异步IO的产生背景
  • Node中的异步IO具体的实现
  • 非I/O的异步API

一.为什么要异步I/O?

Node面向网络而设计,Web应用现如今已不再是单台服务器可以胜任,在跨网络结构下,并发已是现代编程的标配。

以下从“用户体验”和“资源分配”两方面阐述异步I/O的必要性。

1. 用户体验
2. 资源分配

假若业务场景有一组互不相关的任务要完成,有两种方法:

  • 单线程串行依次完成
  • 多线程并行完成

串行执行的缺点:性能上,任意一个稍慢的任务都会导致后续执行代码被阻塞。本可以并行利用CPU等资源,但同步编程模型会因阻塞I/O导致硬件资源得不到更优的使用。

多线程并行的缺点: (1)创建线程和执行期线程上下文切换的开销较大;(2)多线程编程经常面临锁,状态同步等问题。

Node给出的方案:
利用单线程,远离多线程死锁,状态同步等问题;利用异步I/O让单线程原理阻塞,以更好的利用CPU等硬件资源。

二. Node底层是如何实现异步I/O的?

记住四个关键词:

  • 事件循环
  • 观察者
  • 请求对象
  • 线程池

先简洁直白的大致描述一下:

Node发出一个异步调用请求,然后继续执行业务代码;该请求被封装成一个"请求对象"并放入系统的线程池;待有可用线程完成该请求后,将异步结果放入该请求对象,并通知IOCP;每一次"事件循环"中,"观察者"尝试从IOCP中取出可用的"请求对象"并放入事件队列中, 并取出该对象中的回调函数和结果作为一个事件调用执行。

1. 事件循环

Node自身的执行模型----事件循环。

在进程启动时,Node便会创建一个类似while(true)的循环,每执行一次循环体的过程称为Tick。每个 Tick的过程就是查看是否有事件待处理,如有,就取出事件并执行这些相关的回调函数。然后进入下一个循环。如果不再有事件处理,就退出进程。

2. 观察者

正如上文提到,如何判断每个Tick过程中,是否有事件需要处理呢?这就引出了”观察者“。

事件循环是一个典型的“生产者/消费者”模型。异步I/O,网络请求等则是事件的生产者,源源不断的为Node提供不同类型的事件,这些事件被传递到对应的观察者那里。事件循环则从观察者那里取出事件并处理。

在windows下,这个循环基于IOCP创建,而在*nix下则基于多进程创建。

3. 请求对象
  • 以fs.open()为例,javascript调用node的核心模块,核心模块调用C++内建模块,内建模块通过libuv进行系统调用。
  • 系统调用中,对应的是uv_fs_open()方法,该方法创建了一个FSReqWrap请求对象。该对象封装了从javascript层传入的参数,当前方法和回调函数等所有的状态。
  • 系统层将该请求对象放入线程池等待执行。
  • (此时,javascript调用立即返回,即javascript层发起的异步调用的第一阶段到此结束,javascript线程可以继续执行当前任务的后续操作)
4. 执行回调

以上提到,异步I/O的第一阶段:组装请求对象,放入线程池等待执行;
第二阶段:回调通知。

  • 线程池中的I/O操作完成后,会将结果挂载在请求对象上,然后通过PostQuenedCopletedStatus()通知系统层的IOCP,告知当前的操作对象已经操作完成。

  • Node层在每一次Tick的执行中,会调用IOCP的GetQuenedCompletedStatus()方法检查是否有执行完的请求,如果存在则将该请求对象加入事件队列中,并取出结果和回调函数调用执行。

从以上可看出:Windows下主要是通过IOCP来向系统内核发送已完成的I/O调用和从系统内核中取出已完成的I/O操作,配以事件循环,以此完成异步I/O的操作。

三. 非I/O的异步API

  • setTimeout()
  • setInterval()
  • setImmediate()
  • process.nextTick()
1. 定时器

调用setTimeout()或setInterval()创建的定时器会被插入到定时器内部的一个红黑树上。每次Tick执行时,会从该红黑树上迭代取出定时器对象,检查是否超过规定时间,如果超过就形成一个事件,并立即执行其回调函数。

2. process.nextTick()

每次调用process.nextTick()会将回调函数放入一个队列数组中,下一次Tick时取出该数组中全部的回调函数并执行。(时间负责度O(1))

3. setImmediate()

setImmediate()类似于process.nextTick(),都是将回调函数延迟执行。

区别在于:

  • 事件循环对于观察者是有先后顺序的。setImmediate()对应的观察者的优先级低于process.nextTick()对应的观察者。
  • process.nextTick()的回调函数保存在数组中,setImmediate()的回调函数保存在链表中。行为上,processTick()在每轮循环中会将数组中的回调函数全部取出并执行,而setImmediate()在每轮循环中执行链表中的一个回到函数。

佐证代码:

process.nextTick(funciton() {
  console.log('nextTick延迟执行1');
})

process.nextTick(funciton() {
  console.log('nextTick延迟执行2');
})

setImmediate(funciton() {
  console.log('setImmediate延迟执行1');
  process.nextTick(function() {
    console.log('强势插入');
  })
})

setImmediate(fucntion() {
  console.log('setImmediate延迟执行2');
})

console.log('正常执行');

//正常执行
//nextTick延迟执行1
//nextTick延迟执行2
//setImmediate延迟执行1
//强势插入
//setImmediate延迟执行2

四. 总结

事件驱动的本质:通过主循环加事件触发的方式来运行程序。


注:以上均是自己技术栈的整理,仅供备忘。如需交流:stevekeol(微信号)

相关文章

  • 前端进阶全栈-Node的异步IO

    本文力图详尽解释node的异步IO: 异步IO的产生背景 Node中的异步IO具体的实现 非I/O的异步API 一...

  • Node全栈技术开发介绍

    Node全栈技术开发介绍 node和js介绍 node服务端开发 node前端vuejs node前端reactj...

  • 前端进阶全栈 - Node的内存管理

    0.背景 如网页应用,命令行工具等短时间执行的场景,随着进程的退出,内存会释放,几乎没有内存管理的必要。 基于无阻...

  • 我眼中的全栈工程师

    现在越来越多的创业公司都想找全栈工程师,因此市场上就出现了很多伪全栈工程师,特别是学会了Node的前端工程师,前端...

  • NodeJS基础原理

    NodeJS基础原理 异步IO原理浅析及优化方案 异步IO的好处(输入输出input output) 前端通过异步...

  • 自动化构建工具webpack

    随着前端技术的发展,前端开发从静态网页的开发到复杂的前后端交互再到基于node.js的全栈开发,前端需要做的事情越...

  • Node.js+MySQL+Vue 的全栈实战项目

    本项目是一个基于 Node.js 的全栈是实战项目,目标就是带领读者朋友上手实战。众所周知全栈工程师是要比纯前端有...

  • 伪全栈假前端之路 - Vue.js篇

    一、伪全栈假前端的定义 在做好数据库设计,后台开发,前端接口之余,不用学node,npm,webpack等,只要会...

  • nodejs深入学(5)异步编程

    前言 上一章讲解了node如何通过事件循环实现异步,包括与各种IO多路复用搭配实现的异步IO已经与IO无关的异步A...

  • Node.js入门(上)

    Node的好处: 1可以和后端有效沟通2全栈应用3工程化思想 Node.js是什么 是一个异步的事件驱动的js运行...

网友评论

      本文标题:前端进阶全栈-Node的异步IO

      本文链接:https://www.haomeiwen.com/subject/tpiuactx.html