理解异步/同步与非阻塞/阻塞
异步/同步与非阻塞/阻塞其实是两回事。操作系统内核处理I/O只有两种方式:阻塞与非阻塞。
- 阻塞I/O调用之后需要等待系统内核完成所有操作后,调用才能结束。阻塞I/O造成CPU等待,浪费了CPU的处理能力。
非阻塞I/O调用之后会立即返回,CPU的时间片可以用来处理其他事务。立即返回造成的结果是,返回时并没有数据,因为完整数据需要I/O操作完成才能获取,因此需要轮询。
轮询方式有许多种,一个比较好的方式是*nux下的epoll轮询(图片来自书籍):
系统将对任何设备的I/O操作抽象为对文件的I/O。进入轮询时,应用层检查文件描述符上的事件状态,如果没有I/O事件,将会进行休眠,直到事件发生唤醒。这种事件通知机制使得CPU不必花费大量计算用于重复轮询,提高了性能。
遗憾的是,非阻塞I/O是系统内核层面的实现,对于应用程序而言,依然是一种同步。另外,不同系统内核对非阻塞I/O的支持度不一,因此Node使用了线程池模型来实现非阻塞异步I/O。
当主线程需要进行I/O操作时,在线程池创建I/O线程,I/O线程以非阻塞或阻塞I/O的方式向系统内核获取数据。数据获取完成后,通过线程通信异步通知主线程,这样就实现了跨平台的异步I/O。
事件循环
Node进程开启后,会创建一个类似while(true)的循环:
从图中可以看到这个机制类似于android中Looper的设计。每次循环都会检查是否存在事件需要处理,如果有,则取出一个事件,并检查事件是否有相关联的回调。如果有回调,则执行回调。当不存在需要处理的事件时,该循环退出。另一方面,事件源的设计遵循观察者模式。文件I/O、网络请求调用时,会产生一个特定类型的事件,被对应的观察者接收。事件循环检查这些观察者,每次”消费”一个事件。