Node.js 事件循环(Event Loop)介绍

 2016年04月13日    676     声明


JavaScript是一种单线程运行但又绝不会阻塞的语言,其实现非阻塞的关键是“事件循环”和“回调机制”。Node.js在JavaScript的基础上实现,同样是单线程运行的语言。在单线程中要解决高并发的问题,就要采用非阻塞、异步编程的方式。简单的说,就是在非常耗时的I/O操作中,采用非阻塞的方式继续执行后续的代码,并且进入事件循环(Event Loop)。当I/O操作完成,程序会通过回调函数的方式通知原操作。


  1. 事件驱动模式
  2. 事件循环机制

1. 事件驱动模式

事件驱动编程是一种广泛使用的编程模型,事件驱动编程可以理解为通过事件或状态的变化来实现程序流程的控制。

1.1 DOM中的事件驱动

DOM中, 通过添加事件监听的方式来响应'click''change'等事件。

DOM0级中,可以使用以下方式添加事件监听:

var btn = document.getElementById("myBtn");
btn.onclick = function(){
  alert('itbilu.com');
}

DOM2级中,可以使用以下方式添加事件监听:

var btn = document.getElementById("myBtn");
btn.addEventListener("click", showMessage, false);

function showMessage(){
  alert('itbilu.com');
}

DOM对事件的处理,这在本质上是一种事件驱动编程模式。在上面示例中,对按钮添加了'click'事件处理程序(回调函数),在用户点击按钮时会调用指定的处理程序。


1.2 Node.js中的事件驱动

事件驱动模式是Node.js实现高并发关键点之一。Node.js的事件机制由EventEmitter模块实现,Node中很多可以发送事件的核心模块都继承自该模块。

在Web服务器中,我们可以使用以下方式来处理用户请求:

var http = require('http');

//创建一个HTTP服务器
var server = http.createServer();

//监听request事件
server.on('request', function (req, res) { 
  //对request事件的处理	
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World!')
});
server.listen(3000)

在上例中,我们为server添加了'request'事件上监听。当收到用户请求时该事件对应的回调函数会被调用,在回调函数中可以进行用户请求的处理与响应等操作。

传统Web服务器处理用户请求时,用户请求只能依次处理,如果处理时间较长会造成请求的阻塞。不同于传统的Web服务器,事件驱动使得Node创建的Web服务器非常的高效,用户不必须等待上个用户请求处理完成,就可以接收新的请求,并在处理完成后调用对应回调函数响应请求即可。事件驱动回调机制赋予了Node.js强大并发处理能力。


2. 事件循环机制

Node.js的事件循环是靠一个单线程不断地查询队列中是否有事件,当读取到事件时,将调用与这个事件关联的回调函数。上面介绍的事件驱动编程是事件循环的具体表现形式,如:在前面创建Web服务器中,回调函数是一个I/O操作;Node.js会监听3000端口是否有HTTP连接,当收到连接时会启动这个I/O操作,但不会等待操作的完成而是继续查看是否有下一个事件,如果有则继续依次处理。

Node.js的事件循环机制由以下几部分组成:

事件生产者:Node.js通过EventEmitter模块发送事件,发送的事件会被放到事件队列中。

事件队列:事件队列(Event Queue)是一个FIFO模型,一端用于接收推入的事件,另外一端拉出要处理的事件。

事件循环:事件循环(Event Loop)是Node.js事件机制的关键点,它一个单线程运行的任务,会不断轮询事件队列,并将轮询到的事件放到线程池中进行处理。

线程池:线程池(Thread Pool)是真正执行事件和任务处理的位置,比较耗时的操作如:网络I/O、文件操作I/O及其它会引起阻塞的操作都会在这里处理。处理完成后,会调用事件对应的回调函数。

Node.js 事件循环(Event Loop)机制

结合上图及前面介绍可以认为,Node.js单线程运行最直接的体现,是指其在事件循环的时候是单线程运行的。在事件循环的时候,Node线程会把当前事件的读出,并将其放入到线程池处理,然后开始下一事件处理。

在Node的底层有一个libuv库,它实现了事件循环线程池等功能。线程池是系统级别任务处理,也就是说事件的实际处理里是在更底层系统级完成的,在这一过程中并不是单线程的。处理完成后,线程池会调用与之绑定的回调函数,这样处理结果又被传回到了Node中。