I/O
就我们所知,I/O主要分为,异步I/O和同步I/O,阻塞I/O和非阻塞I/O。其实,准确的说,只有阻塞I/O和非阻塞I/O,异步I/O一般都是通过线程池的方式来模拟这个效果的。那么,阻塞I/O和非阻塞I/O是什么呢?
阻塞I/O
阻塞I/O其实就是,当你发起了I/O操作后,你必须要等到I/O操作完成,结果返回之后,你才可以继续执行,这也说明了,在发起I/O请求再到I/O结果返回的这一段CPU时间就浪费了,那么有没有办法可以使用这一段CPU时间呢?答案就是使用非阻塞I/O。具体的可以看下图..jpg)
非阻塞I/O
前面我们说过,当发出I/O请求后,我们就必须一直等待I/O结果返回,那么,我们可以发出I/O请求后,就去做其他事情,等过会儿再来看一下吗?答案是可以的。非阻塞I/O就是这样做的,当发出I/O请求后,内核立马返回一个文件描述符,我们可以通过文件描述符在之后去对读取文件的数据以及操作文件。
非阻塞I/O实现方式
其实基本上所有非阻塞I/O的实现方式都是基于轮询的。轮询是意思是说,我发出请求后,我就先利用CPU去做其他的事情,不过每隔一段时间我就来问你,我的请求是否已经完成了。这样一来的话,确实我们就可以合理的利用这一段限制的CPU时间了。但是,其实对于应用程序来说,这也算是一种同步,因为应用程序需要等待I/O结果完全返回之后,才能做其他事情。依旧花了很多时间在等待,在等待期间内,CPU要么用于遍历文件描述符的状态,要么用于休眠等待事件。
那么,有没有一种方法,可以真正实现我们所想要的效果呢?也就是发出I/O请求后,可以立刻执行下面的语句,等到I/O操作完成后,在执行相应的回调函数呢?单线程来说的话,可能就很难。不过对于多线程来说就比较简单了。我们可以让一些线程专门用于I/O操作,这里的I/O操作可以是阻塞的,也可以是非阻塞的。而有一个主线程专门来执行语句。也就是说,主线程来执行语句,当遇到I/O操作后,则将I/O操作分配给专门负责I/O操作的线程,然后自己继续执行下面的语句,当I/O操作完成后,则操作线程通过线程间的通信通知主线程,并将结果返回给主线程,主线程在将结果和回调函数混合起来执行。这样,就可以达到我们想要的效果了。
其实,nodejs的非阻塞I/O就是这么实现的。它有四个基本元素:请求对象,I/O线程池,观察者,事件循环。当你发出一个I/O请求的时候,则会将你的请求的参数、回调函数等,封装在一个请求对象上,然后将其放到I/O线程池中等待执行。当I/O线程池中有线程可用的时候,则将线程分配给请求对象,执行相应的I/O操作,当I/O操作完成后,则将I/O操作的结果放到请求对象中,并通知I/O观察者I/O操作已完成,请求对象会被加入到I/O观察者的请求对象队列中,在每一个事件循环开始,主线程都会观察I/O观察者的队列是否为空,若不为空,则取队首的请求对象,取出请求对象的回调函数和结果,并执行回调函数。