NodeJs异步IO

一、异步的应用

对于前端开发人员来说,异步编程再普遍不过了(ajax)。

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

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

二、异步和同步的比较

在java中如果需要发起网络请求,假设我们已经封装好了一个方法,如下:

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…)就算与网络请求返回结果无关也必须要等待。

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

同步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就很有必要了。

三、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提供不同类型的事件,这些事件被传递到对应的观察者那里,事件循环则是从观察者那里取出事件并处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值