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

2020-01-11  本文已影响0人  stevekeol

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

一.为什么要异步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. 请求对象
4. 执行回调

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

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

三. 非I/O的异步API

1. 定时器

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

2. process.nextTick()

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

3. setImmediate()

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

区别在于:

佐证代码:

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(微信号)

上一篇 下一篇

猜你喜欢

热点阅读