Node中的异步i/o和事件轮询机制

Posted by mieruko on 2016-11-30

前言

这篇文章既然写了,接下来的一段时间我就会不断更新它。关于Node中的一些机制,因为我暂时还没有一个非常扎实的操作系统基础,眼下主要是通过《深入浅出Node.js》和一些网上的文章来理解它们。《计算机操作系统》这本书我还没有读完,但是我想写一下这个阶段自己对这些东西的一个理解,顺便记录一下自己困惑的地方,以后来填坑。

12-02填坑

异步时Node平台开辟的线程池里的i/o(或网络请求之类的需要等待的线程),这些线程所在的进程随着线程的阻塞会一起进入阻塞状态,阻塞状态的进程是不占用cpu的,这正是Node节省时间的关键。至于事件循环对时间的占用,和阻塞对时间的浪费比起来应该是微乎其微的,这也正是Node异步高性能的关键所在。

从所谓“单线程”说起

我们常常听到的一句话是”javascript是单线程的”,然而这句话其实应该是”javascript的主线程师单线程的“,怎么理解呢。我记得在java里面,通过类的继承,我们是可以利用这个语言自己去开辟线程的。然而javascript不可以,整个应用层就只有一个线程从头到尾执行下来。
因此我们要说”javascript中开发者的代码是单线程的(只能运行在主线程中)“。

异步的实现

第一次接触到js的异步,是浏览器端的Ajax请求。当时就对上面的单线程有了困惑,查了资料之后知道虽然js是单线程的,但是浏览器是多线程的,因此可以一边运行主线程,一边处理http请求,对http的请求其实相当于开了一个新线程。

(这里我有一个疑问,往往认为ajax异步是“不影响”主线程运行的,但是线程与线程之间,微观上应该是有切换的,同一时间一个进程中只能有一个线程在运行,因此切换来切换去,虽然实现了并行,但是总的运行时间并没有减少啊,所以我暂时认为异步会一定程度上放慢主线程的执行速度。如果以后有了新的想法,再回来改正。)
(楼上的坑已填 在最上面)

Node也是如此,js是单线程的,但是Node这个平台是多线程的,除了我们的主线程,还有负责处理I/O操作的线程池,还有事件循环线程。

因此,异步的实现,是依赖于平台的多线程的。

事件轮询机制

事件循环

在进程启动时,Node就会开辟一个线程,这个线程用来执行一个类似于while(true)的循环,每执行一次循环体的过程就称之为Tick。每一次Tick的时候就查询看是否有待处理的事件,有的话就取出事件和相关的函数,如果存在关联的回调函数,就执行它们。

以上这段选自朴灵的《深入浅出Node.js》,我觉得写得挺形象的,就直接拿来用了。

线程池

Node的异步i/o是在线程池中进行的,线程池是Node调用它底层的c++模块实现的。

一个异步调用的开始到结束

主线程抛出一个异步i/o请求之后,封装请求对象,设置参数和回调函数,然后将请求对象放入线程池等待执行。
放入线程池这一步可以理解为开辟了一个i/o操作的新线程,然后主线程继续往下走,新线程执行i/o操作,事件循环仍然在不停地检查事件队列里面有没有未执行的事件。
某一个时刻,执行i/o操作的线程完成了任务,它就会把执行结果和相应的回调函数一起放进事件队列,这个线程就结束了。
事件循环按照事件队列顺序执行队列里的回调函数和事件结果,轮到到事件队列里面新出现的这个事件信息和回调函数时,就连事件信息和回调函数一起取出并且执行。