异步 I/O
异步 I/O 实现现状
异步 I/O 与 非阻塞 I/O
- 操作系统内核对于 I/O 只有两种方式:阻塞与非阻塞
- 阻塞 I/O 造成 CPU 等待浪费,非阻塞则需要轮询去确认是否完全完成数据获取,让 CPU 处理状态判断,对 CPU 资源造成浪费
现存轮询技术
- read
- select
- poll
- epoll
- kqueue
理想的非阻塞异步 I/O
完美的异步 I/O 应该是应用程序发起非阻塞调用,无须通过遍历或者事件唤醒等方式轮询,可以直接处理下一个任务,只需在 I/O 完成后通过信号或回调将数据传递给应用程序即可。
Node 异步 I/O
- 事件循环
观察者
- 每个事件循环中有一个或者多个观察者,而判断是否有事件要处理的过程就是向这些观察者询问是否有要处理的事件
- 事件循环是 生产者/消费者 模型。异步 I/O、网络请求等是事件的生产者,源源不断为 Node 提供不同类型的事件,这些事件被传递到对应的观察者那里,事件循环则从观察者那里取出事件并处理。
请求对象
- JS 发起调用到内核执行完 I/O 操作的过程中,存在一种中间产物——请求对象
执行回调
- I/O 观察者回调函数的行为就是取出请求对象的 result 属性作为参数,取出 oncomplete_sym 属性作为方法,然后调用执行,以此达到调用 JavaScript 中传入的回调函数的目的
非 I/O 的异步 API
- 定时器
setTimeout()
setInterval()
process.nextTick()
// 立即异步执行一个任务
setTimeout(function() {
// Todo
}, 0); // 需动用红黑树、创建定时器对象和迭代等操作,较为浪费性能
process.nextTick(function() {
// Todo
}); // 更高效 复杂度为 O(1)
setImmediate()
- 和
process.nextTick()
类似,但是其属于 check 观察者,回调优先级低 process.nextTick()
的回调函数保存在一个数组中,而setImmediate()
的结果是保存在链表中。process.nextTick()
在每轮循环中会将数组中回调全部执行,而setImmediate()
在每轮循环中执行链表中的一个回调函数
- 和