NodeJs异步IO

1、异步的应用

对于前端开发人员来讲,异步编程再广泛不过了(ajax)。javascript

对于服务端(java)开发人员来讲,也存在异步编程,相关编写可参见Java分类下的“java编写异步处理方法提高系统性能”该篇文章,这里也就不放传送门了。前端

对于移动端全部的触发事件也都是异步的,相关开发人员应该也都是很熟悉的。java

2、异步和同步的比较

在java中若是须要发起网络请求,假设咱们已经封装好了一个方法,以下:node

Log.log("发起网络请求");

JSONObject result1 = HttpUtil.get("http://host:port/uri1");

Log.log("网络请求1返回内容:" + result1.toString());

JSONObject result2 = HttpUtil.get("http://host:port/uri2");

Log.log("网络请求2返回内容:" + result2.toString());

// do something...

以上代码是顺序执行的,也就是说网络状况不好的环境下,后续的操做(do something…)就算与网络请求返回结果无关也必需要等待。web

下图说明一下同步IO的执行顺序:ajax

同步IO调用示意图

因此以上代码的总用时=网络请求1的用时时长+网络请求2的用时时长编程

下面咱们看看使用javascript的异步请求具体状况:网络

console.log("发起网络请求");

request('http://host:port/uri1', function (error, response, body) {
    if(error) return console.error(error);

    console.info('网络请求1返回内容', body);
});

request('http://host:port/uri2', function (error, response, body) {
    if(error) return console.error(error);

    console.info('网络请求2返回内容', body);
});

console.log('do something...');
// do something...

以上代码会先输出发起网络请求,而后是do something...,再而后的输出就要看这两个网络请求哪个最早完成了。架构

下图说明一下异步IO的执行顺序:异步

异步IO调用示意图

以上代码总用时=Max(‘网络请求1用时时长’, ‘网络请求2用时时长’);

以上只是两次IO的比较,若是应用存在大量的IO操做,这个性能的差距就是巨大的,异步IO的优点也就凸显出来了。尤为是在如今微服务盛行的年代,一个应用须要调用多方服务,这时使用异步IO就颇有必要了。

3、Node异步IO的实现

众所周知,javascript是单线程执行的,也就是说全部的非IO请求的代码都是在主线程内执行,可是当咱们发起IO请求时,该IO调用就不是在主线程执行了,否则主线程就会被阻塞掉,没法响应其它事件。那么在咱们发起IO请求时,具体是怎么个执行过程呢?看下图:

异步IO调用

上图代表了当咱们发起IO请求时,调用的是各个不一样平台的操做系统内部实现的线程池内的线程。这里的IO请求可不单单是读写磁盘文件,在*nix中,将计算机抽象了一层,磁盘文件、硬件、套接字等几乎全部计算机资源都被抽象为文件,文章中说的IO请求就是抽象后的文件。

那么可能有人又会存在疑问了,Node是怎么实现跨平台的,不一样的操做系统实现的线程池应该是不一样的,这里咱们再放一张图:

基于libuv的架构示意图

上图能够看出,Node是基于libuv封装层上运行来实现跨平台兼容的,全部平台兼容性的判断都由这一层来完成,并保证上层的Node与下层的自定义线程池及IOCP之间各自独立。Node在编译期间会判断平台条件,选择性编译unix目录或是win目录下的源文件到目标程序中。这一点就像java是基于jvm运行同样,jvm实现了底层操做系统的差别性。

以上说的都是Node实现异步IO的系统支持,下面咱们来具体看下它的内部实现。以下图:

Node实现异步IO的流程

如上图所示,事件循环、观察者、请求对象、IO线程池这四者共同构成了Node异步IO模型的基本要素。

主线程内操做:主线程发起异步IO调用,而后将请求参数(文件路径filePath、回调函数callback等)等信息封装到请求对象上,而后将请求对象放入请求队列,等待线程池给该请求分配可用线程。

线程池内操做:线程池有可用线程,取出请求队列内请求对象,对其分配线程。在分配的线程内执行请求对象中IO操做,执行完成后会将执行结果result封装到请求对象上,并通知线程池IO操做已完成,而后将该线程归还给IOCP线程池。

事件循环内操做:相似于while(true),获取已完成IO操做的IO事件,并触发该事件。相对应的IO观察者会观察到该事件,并获取请求对象,该请求对象上已经封装了请求时的回调函数callback和IO操做的执行结果result,IO观察者取出回调函数和IO执行结果并调用执行callback(error, result)。注:这里说的相对应的IO观察者是针对不一样类型会有不一样的观察者,例如网络请求IO观察者、文件读写IO观察者等,观察者将事件进行了分类。

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