Node学习————异步IO

首先,异步是什么呢?
异步是像对于同步而言的。异步通常带来的是非阻塞,能减少等待的时间和计算机硬件空闲的时间,在单位时间内处理更多的任务,而同步带来的是阻塞,一个同步队列中两个任务,当第一个在处理时,另一个必须等待,等到第一个任务处理完毕后,才加载第二个任务。关于同步,我们可以以在餐厅买饭为例。餐厅的卖饭窗口有三个工作人员,一个负责刷卡,一个负责打饭,一个负责把饭递给顾客。同步的情况下,第一个人买饭,先刷卡,后在窗口前等待拿饭。刷完卡后,刷卡的工作人员处于空闲状态,而后边的顾客此时还需要一直等待。等到第一个顾客拿到饭离开窗口以后,第二个顾客开始刷卡,重复第一个顾客的行为。这样一个顾客买饭的平均时间=刷卡时间+打饭时间+递饭时间。当然,这样做的一个好处是不会发生混乱,每个顾客都能正确地拿到自己的饭。然而,这样极大地浪费了后边顾客的时间,现实中一般是不会有窗口这么做的,这就是同步。异步呢,我们可以以去电脑店修电脑为例。我们把坏了的电脑搬到电脑店,这时商家说一时修不好,等修好了给我们打电话再去取。之后,我们不用一直待在电脑店等待修电脑的过程,我们可以去办别的事情。等到电脑修好了我们收到了商家的电话,再去取电脑。这样极大地节省了我们的时间。

那么异步具体应用在什么地方呢?
有电脑常识的人会直到,在计算机中,数据I/O是比较耗时的一项工作。获取文件数据时,内核需要获取文件描述符,根据文件描述符去磁盘对应的柱面磁道上寻址,读取数据,然后将数据返回。这这段时间内,如果是同步的情况下,当前任务会一直阻塞,而此时的cpu处于空闲状态,无疑造成了系统硬件资源的浪费。如果此时是异步读取的话,第一个只需要注册一个文件I/O事件,传入回调函数,然后继续后续代码的执行。当文件读取完成时,再调用回调函数返回数据完成文件读取。这样一来,能极大地提高cpu的利用率。同样,网络套接字的I/O也是一样。
不仅在node中,在浏览器中异步调用也很常见。在我们调用数据接口时,会越来越多地用到ajax来做局部刷新提升用户体验,ajax是指异步网络请求,我们在使用时,只需要配置好url,headers,callback等参数,就能够调用了。如果网络请求是同步的话,我们从发送请求到收到数据这段时间,就不能与页面上的ui进行交互,浏览器会处于一种假死的状态,极大地影响用户体验。使用ajax,我们创建了网络请求后,就可以接着执行其它代码了。等到数据返回时,会执行传入的回调函数来进行后续操作。

Node中异步I/O的执行过程是怎样的呢?
它的执行过程,离不开事件循环、观察者、请求对象、线程池这几部分。在javascript代码执行过程中,以异步读取文件为例,首先调用fs.readFile发起异步调用,通过传入的参数和回调封装请求对象,之后将请求对象放入线程池等待执行。当有空闲线程时,线程会加载这个请求对象,执行请求对象中的I/O操作,取回数据后通知内核,内核会将处理后的I/O交给事件循环中的I/O观察者,在事件循环中进行回调函数的执行。整个过程中,在javascript执行线程上是异步的,但是针对于线程池里单个线程而言是同步的。

Node服务器能够很好地处理高并发的情况,和它的异步也不无关系。当node服务器运行时,它通过监听网络套接字中的请求,然后形成事件交给I/O观察者。事件循环会不停地对观察者进行遍历,不断地处理各种网络I/O请求。如果javascript中有回调事件传入时,会将通过取得的数据返回给回调函数,传到业务逻辑层进行处理。多个请求不用依次等待上一个请求完成后才进行下一个请求,这样极大地节省了时间,对用户的相应也更加迅速。

此外,除了异步I/O,还有一些与异步有关的api,比如setTimeout,setInterval, setImmediate,process.nextTick,这些在前端和node中也较为常用。以后再讲。