Nodejs (4) 异步I/O

Submitted by Lizhe on Thu, 01/04/2018 - 12:32

单线程同步编程模型会因阻塞I/O导致硬件资源得不到更优的使用

多线程编程模型也因为编程中的死锁, 状态同步等问题让开发人员头疼.

Node在两者之间给出了它的方案, 利用单线程, 远离多线程死锁, 状态同步等问题, 利用异步I/O, 让单线程远离阻塞, 以更好的使用CPU

异步I/O可以算做Node的特色 , 因为它是首个大规模将异步I/O应用在应用层上的平台, 它力求在单线程上将资源分配得更高效

 

为了弥补单线程无法利用多核CPU的缺点, Node提供了类似前端浏览器中Web Workers的子进程, 该子进程可以通过工作进程高效地利用CPU和I/O

 

Node 自身的执行模型为 事件循环, 正是它使得回调函数十分普遍

在进程启动时, Node便会创建一个类似于while(true)的循环, 每执行一次循环体的过程我们称为Tick. 每个Tick的过程就是查看是否有事件需要处理, 如果有, 就取出事件及其及相关的回调函数. 如果存在相关的回调函数, 就执行它们, 然后进入下一次循环, 如果不再有事件处理, 就退出进程.

 

观察者

在每个Tick的过程中如何判断是否有事件需要处理呢, 这里必须要引入的概念是观察者

每个事件循环中有一个或者多个观察者, 而判断是否有事件要处理的过程就是向这些观察者询问是否有要处理的事件

事件循环是一种典型的 生产者/消费者 模型,   异步I/O,网络请求等是事件的生产者, 这些事件被传递给观察者, 事件循环则从观察者哪里取出事件并处理.

在windows下, 这个循环基于IOCP创建, 在Unix和Linux下则基于多线程创建

 

从Javascript发起调用到内核执行完I/O操作的过程中, 存在一种中间产物, 它叫做请求对象

timg

事件循环, 观察者, 请求对象, I/O线程池这四者共同构成了Node异步I/O模型的基本要素

同步式

对于同步式的服务,一次只能处理一个请求, 并且多余请求都处于等待状态

每进程/每请求

为每个请求启动一个进程, 这样可以处理多个请求, 但是它不具备扩展性, 因为系统资源只有那么多

每线程/每请求

为每个请求启动一个线程来处理. 尽管线程比进程要轻量, 但是由于每个线程都占用一定内存, 当大并发请求到来时, 内存将会很快用光, 导致服务器缓慢.

 

每线程/每请求的方式目前还被Apache所采用. Node通过事件驱动的方式处理请求,无须为每个请求创建额外的对应线程, 可以省掉创建线程和销毁线程的开销, 同时操作系统在调度任务时,因为线程较少, 上下文切换的代价很低. 这使得服务器能够有条不紊地处理请求, 即使在大量连接的情况下,也不受线程上下文切换开销的影响