/ node

Node学习笔记之异步I/O

首先搞清楚几个概念

1.what is the I/O?

输入输出(Input/Output)?

I/O操作包括读写操作、输入输出、请求响应等等。

2.阻塞I/O,非阻塞I/O

程序在执行时,或多或少都会有I/O调用,而操作系统内核对于I/O有且只有两种机制

  • 阻塞
  • 非阻塞

阻塞I/O的特点就是调用之后一定要操作系统内核层面完成所有操作后,才调用结束

  • issue:造成CPU等待浪费,以及后续其它业务调用停滞

非阻塞I/O不同与阻塞I/O,它调用之后立刻返回,但是只是返回一种状态,不带任何数据,若要获取完整的数据,应用程序需要重复调用I/O操作来确认是否完成

  • issue:让CPU处理状态判断,是对CPU资源的浪费
  • 有关与这种重复调用判断操作是否完成的技术叫做**轮询(evevt loop)**现存的轮询技术主要有以下5种:
  • read
  • select
  • poll
  • epoll
  • kqueue

So,阻塞与非阻塞是属于操作系统(线程/进程)层面进行等待的一种方式

3.异步&同步

同步:**Keep waiting.**代码在执行时一切都得按顺序来,即使遇到时间开销大的调用,依旧需要等待调用完成,然后才会执行余下代码,(如下码,事先在1.txt里面输入“Oh,你打开了1.txt文件!”)

var fs    = require('fs');
var data  = fs.readFileSync('1.txt', 'utf-8');

console.log(data);
console.log('Hi,程序结束\n');

执行代码,如下输出:
Alt text

异步:**Don’t call me, I will call you.**代码在执行时,如遇到时间开销大的调用,无需等待该调用完成,继续执行余下的代码,(如下码)

var fs = require('fs');

fs.readFile('1.txt', 'utf-8', function(err, data) {
  if (err) {
    console.log(err);
  }else {
    console.log(data);
  }
});

console.log('Hi,程序结束\n');

执行,输出:
Alt text

So,由此可见,异步与同步是代码层面的消息通知机制


此外,关于同步和异步,阻塞和非阻塞,网上大神的概述:
同步/异步是 API 被调用者的通知方式。阻塞/非阻塞则是 API 调用者的等待方式。


OK,理解以上概念后,先不慌再来理解一下

  • 同步阻塞
  • 同步非阻塞
  • 异步阻塞
  • 异步非阻塞

就不啰嗦了,直接paste网上大神的总结,非常暴力直接的解释:(拿小明下载东西来举例)

  • 同步阻塞:小明一直盯着下载进度条,到 100% 的时候就完成。
  • 同步非阻塞:小明提交下载任务后就去干别的,每过一段时间就去瞄一眼进度条,看到 100% 就完成。
  • 异步阻塞:小明换了个有下载完成通知功能的软件,下载完成就“叮”一声。不过小明仍然一直等待“叮”的声音(看起来很傻,不是吗)
  • 异步非阻塞:仍然是那个会“叮”一声的下载软件,小明提交下载任务后就去干别的,听到“叮”的一声就知道完成了。
  • 全文地址

小结:所以,从实际效果而言,异步和非阻塞都可以达到我们并行I/O的目的,但实际上却是两回事。

而效率最高的I/O操作莫过于异步非阻塞I/O(当然只是理想中的...)


回到Node中的异步I/O

node实现异步I/O的主要环节

  • 事件循环
  • 观察者
  • 请求对象
  • 执行回调
事件循环

node自身的执行模型为事件循环,这使得回调函数在node中非常普遍性。
在进程启动时,node便会创建类似于while(true)的循环,每执行一次循环的过程称之为Tick。每个Tick的过程就是查看是否有事件要处理,如果有就取出事件以及相关回调函数。如果存在关联的回调函数,就执行它们。然后进入下个循环,如果不再有事件处理,就退出进程。

观察者

在每个Tick的过程中,就得需要判断是否有事件需要处理,由此而引入了观察者 的概念。
每个事件循环中就有一个或者多个观察者,而判断是否有事件要处理的过程就是向这些观察者询问是否有要处理的事件。
这个过程就好比饭馆厨房一轮一轮地制作菜肴,但是具体要做那些菜做几道,就得取决于前台收银接到的客人的单子了。在这里收银就是观察者,收到的点单就是关联的回调函数,当然饭馆经营得当,就可能会有多个收银,就如同事件循环中有多个观察者一样。
收到下单就是一个事件,一个观察者手中就可能有多个事件。

请求对象

对于node中的异步I/O调用而言,回调函数却不由开发者来调用。事实上,从js发起调用到内核完成I/O操作的过渡过程中,存在一种中间产物,他叫请求对象
请求对象是异步I/O过程中的重要中间产物,所有的状态都保存在这个对象中,包括送入线程池等待执行以及I/O操作完毕后的回调处理。

执行回调

组装好请求对象,送入I/O线程池等待执行,实际上完成了异步I/O的第一步,回调通知是第二部分。


小结:事件循环是异步实现的核心


最后的的最后

Everything runs in parallel except your code!
(在Node中)除了代码,一切都是并行的!

参考资料:

  • internet
  • 《深入浅出 Node.js》