# 异步IO
它的优秀之处并非原创, 它的原创之处并不优秀
- 浏览器中的JavaScript在单线程上执行,与UI渲染共用一个线程
- 前端通过异步可以消除UI阻塞的现象
# 异步I/O 与 非阻塞I/O
# 非阻塞I/O (单线程)
- 为了获取完整的数据,应用程序需要重复调用I/O操作来确认是否完成 (轮询)
# 轮询技术的演变 (休眠时,cpu是闲置的,不理想)
read
原始的、性能最低select
通过对文件描述符上的事件状态进行判断- 采用 1024 长度的数组来存储状态
- 最多可以同时检查 1024 个文件描述
poll
链表的方式避免数组长度的限制,避免不需要的检查,- 文件描述符较多的时候,性能十分低下
epoll
在进入轮询的时候如果没有检查到I/O事件,将会进行休眠,直到事件唤醒它。- 利用了事件通知、执行回调的方式
- 不是遍历查询
- 不会浪费CPU,执行效率高
# Linux --> AIO
- 通过信号或者回调来传递数据的 (实现异步I/O)
- 缺点
- 只有 Linux 下有
- 仅支持内核I/O中的O_DIRECT方式读取, 导致无法利用系统缓存
# 异步I/O (多线程)
- 通过让部分线程进行阻塞I/O或者非阻塞I/O加轮询技术来完成数据获取,让一个线程进行计算处理,通过线程之间通信将I/O得到的数据进行传递。实现异步I/O(模拟的)
# Node 的异步 I/O
- 完成整个异步I/O环节的 ---> 事件循环、观察者、请求对象
# 事件循环
- 进程启动时, Node 创建一个类似while(true)的循环事件
- 每一次执行循环体的过程称为Tick
- 每个Tick过程就是查看是否有事件待处理
- 有就取出事件及相关的回调函数
- 如果存在相关联的回调函数,就执行它
- 进入下一循环, 不在有事件处理,退出进程
# 观察者
- 每个事件循环中有一个或者多个观察者,而判断是否有事件要处理的过程就向这些观察者询问是否有要处理的事件
# 请求对象
- 一般的(非异步)回调函数, 函数由我们自行调用
var forEach = function (arr, callback) {
for (var i = 0; i < list.length; i++) {
callback(list[i], i, list)
}
}
- node中的异步I/O调用,回调函数不由开发来调用
- javascript发起调用到内核执行完成I/O操作的过渡过程中,存在一种中间产物 --- 叫做
请求对象
- 从javascript调用 ---> node核心模块 ---> c++内建模块 ---> 通过libuv进行系统调用 (node的经典调用方式)
- 请求对象是异步I/O过程中的重要中间产物,所以的状态都保存在这个对象中,包括送入线程池等待执行以及I/O操作完毕或的回调处理
# 执行回调
- 组装好请求对象、送入I/O线程等待执行 --> 是 完成
异步I/O的第一部分
- 回调通知 -->是
第二部分
小记
Node异步I/O模型的基本要素
- 事件循环
- 观察者
- 请求对象
- I/O线程池
javascript是单线程的,node是多线程的
# 非 I/O 的异步API
- setTimeout()
- setInterval()
- setImmediate()
- process.nextTick()
# 定时器
- 不需要I/O线程池的参与
- 创建的定时器会被插入到定时器
观察者
内部的红黑树中 - 每次Tick执行时,会取出定时器对象, 检查是否超过时间
- 如果超过,就形成一个事件,它的回调函数将立即执行
- 它并非精确的(在容忍的范围)
# process.nextTick()
- 说明
- setTimeout(fn, 0) 需要动用红黑树,较为浪费性能 --- 时间复杂度 O(lg(n))
- nextTick() --- 时间复杂的 O(1)
- 每次调用process.nextTick(),只会将回调函数放入队列中
- 在下一轮Tick时取出执行
# setImmediate()
- process.nextTick() 回调函数保存在
数组中
- setImmediate() 结果保存在
链表中
- 每轮循环中执行链表中的一个回调函数
- 顺序执行
- 保证每轮循环都能较快执行
- 防止cpu占用过多而阻塞后续的I/O调用情况