Node.js最大的特点就是异步式I/O(非阻塞I/O)与事件紧密结合的编程模式。
一、异步式I/O和同步式I/O
1.什么是阻塞式I/O:线程在执行过程中遇到磁盘读写或网络通信(统称为I/O操作),通常要耗费很长的时间,这时系统会剥夺这个线程的CPU控制权,使其暂停执行同时将资源让给其他的工作线程,这种调度方式称为阻塞。当I/O操作完毕时,操作系统将这个线程的阻塞状态解除,恢复其对CPU的控制权,令其继续执行,这种I/O模式就是同步式I/O或阻塞式I/O。
2.异步式I/O或非阻塞式I/O:(针对所有的I/O操作不采用阻塞的策略)当线程遇到I/O操作时,将I/O请求发送给操作系统,继续执行下一条语句,当操作系统完成I/O操作时,以事件的形式通知执行I/O操作的线程,线程会在特定的时候处理这个事件。为了处理异步I/O,线程必须有事件循环,不断的检查有没有待处理事件,依次予以处理。
3.在非阻塞模式下,一个线程永远在执行计算操作,这个线程所使用的CPU核心利用率永远是100%,I/O以事件的方式通知,并且线程不会被阻塞,永远都在利用CPU。
4.单线程事件驱动的异步式I/O与比传统的多线程阻塞式I/O优点和缺点:异步式I/O就是少了多线程的开销,当然异步式编程缺点是不符合人们程序设计思维,让控制流变得晦涩难懂。
例子:
新建一个file.txt文件,里面随便输入文字即可,和下面js保存在同一目录中。
(1)异步式读取文件
新创建一个AsynchronousRead.js文件
var fs = require('fs');
fs.readFile('file.txt','utf-8',function(err,data){
if(err){
console.error(err);
}else{
console.error(data);
}
});
console.log('end.');
在cmd中运行输出结果是:
(2)同步式读取文件
新建一个SynchronousRead.js文件
var fs = require('fs');
var data = fs.readFileSync('file.txt','utf-8');
console.log(data);
console.log('end.');
在cmd中运行输出结果是:
存在的差异是因为:fs.readFIle调用时所作的工作只是将异步式I/O请求发送给了操作系统,然后立即返回并执行后面的语句,执行完成以后进入事件循环监听事件。当fs接收到I/O请求完成事件时,事件循环会主动调用回调函数以完成后续工作,因此会先看到end.再看见file.txt文件内容
二、事件
Node,js所有的异步I/O操作在完成时都会发送一个事件到事件队列。
1.事件由EventEmitter对象提供。用一个例子说明:
创建一个event.js
var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();
event.on('some_event',function(){
console.log('some_event occured.');
});
setTimeout(function(){
event.emit('some_event');
},1000);
原理是:event对象注册了事件some_event的一个监听器,然后通过setTimeout在1000毫秒以后向event对象发送事件some_event,此时会调用some_event的监听器。
2.Node.js的事件循环机制
事件的回调函数在执行过程中,可能会发出I/O请求或直接发射(emit)事件,执行完毕后再返回时间循环,时间循环会检查事件队列中有没有微处理的事件,直到程序结束。
Node.js的事件循环对开发者不可见,由libev库实现。libev时间循环的每一次迭代,在Node.js中就是一次Tick,libev不断检查是否有活动的、可供检测的事件监听器,直到检测不到时才退出事件循环,进程结束。