JS执行是单线程的,但Node底层有一个线程池,使用多线程来实现异步I/O
Node的异步I/O
几大要素:事件循环、观察者、请求对象、I/O线程池。事件循环
进程启动,会创建一个while(true)的事件循环,每次是一个Tick,每个Tick就是查看是否有事件待处理,有事件则取出事件及回调,执行回调,没有则退出进程。
观察者
事件循环判断是否有事件待执行,就是通过询问观察者,观察者不会主动通知进程,而是进程询问时才返回结果。
事件循环是生产者/消费者模型。异步I/O和网络请求是事件的生产者,这些事件被输送到观察者,事件循环是消费者,从观察者这里取出事件处理。观察者就相当于模型中的缓冲区,生产者不断产生事件放到缓冲区,而消费者从缓冲区里取出事件进行消费。请求对象
Node底层实现异步I\O,依赖请求对象。
假设fs.open(),根据指定路径和参数打开文件,整个调用过程
请求对象就在libuv这个过程创建,传入参数和当前方法都包含在这个对象中,然后再将改对象推入进程池中。
JS层面的请求会立即返回,继续执行后续的操作,而上面的操作都是在node层进行的,这个打开文件的请求会依赖请求对象在线程池中等待执行,执行完毕后执行相应的回调函数,而JS执行线程并不关心下面线程池里发生的事,当前的I/O操作不管是否阻塞I/O,都不会影响JS执行线程的执行,这就是异步。
总之,JS执行线程是单线程,而NODE底层有个线程池,这里进行多线程使用事件循环,实现异步I/O
请求对象既包括异步I/O的所有状态,从送入线程池到I/O操作后执行的回调。执行回调
送入线程池是第一步,调用回调是第二步。关键是事件循环
I/O执行结束后,会将结果通知IOCP(windows下,linux下epoll),并将线程归还线程池。使用事件循环的I/O观察者,如果有已经执行完的I/O,则将请求对象加入I/O观察者队列中,事件循环再Tick过程中,检测到有I/O观察者,则取出其中的请求对象,再取出请求对象中的回调函数执行。