node中的 “event loop” 正是node能处理高并发的核心所在。也正是因为它,node虽然在本质上是个单线程,却能让大量的操作处于后台运行。这篇文章将详细说明 event loop 的运行机制。
事件驱动编程
为了理解 event loop
,首先得明白事件驱动编程的概念。事件驱动编程的概念在20世纪60年代就广为人知。在今天,它被广泛应用于 UI
编程中。既然JavaScript
最主要的作用是操作DOM
,那么使用基于事件的 API
就再自然不过了。
简单来说,基于事件驱动的编程就是程序的控制流程基于事件或者事件状态的转变。常见的实现方式是定义一个核心机制用来监听事件,当所监听的事件发生时,或者状态发生改变时,回调注册在该事件上的回调方法。其实这也正是event loop背后的基本原理。
如果你熟悉前端js开发,你可能会想起 .onXxx()
方法,例如 element.onclick()
,这些方法正是监听DOM
元素上用户的操作。这种模式适用于单个元素可能会触发许多不同的事件的情景下。Node中的 EventEmitter
正是使用这种模式,它存在于node的Server
,Socket
和 http
等多个模块中。当我们需要从一个对象中发送多种状态变化时,这种模式就非常好用。
另外一种通用的模式是成功状态的回调与失败状态的回调。目前主要有两种实现方式,一种是错误优先的回调的模式:即发生错误时,错误信息作为第一个参数传递给回调方法。另外一种方式是ES6
中的Promises
。
Node中的fs
模块主要是使用错误回调优先的方式。从技术上来讲,对于某些调用,它可以发送额外事件,例如 fs.readFile()
,但是API设计成只有当用户指定的操作成功与失败时才会通知用户。API之所以设计如此,只是出于架构上的考虑而非技术的限制。
一个常见的错误理解是,认为事件的发送器是异步的,其实不然,下面这个例子就能很好的说明:
function MyEmitter() {
EventEmitter.call(this);
}
util.inherits(MyEmitter, EventEmitter);
MyEmitter.prototype.doStuff = function doStuff() {
console.log('before')
emitter.emit('fire')
console.log('after')}
};
var me = new MyEmitter();
me.on('fire', function() {
console.log('emit fired');
});
me.doStuff();
// 输出结果为:
// before
// emit fired
// after
EventEmitter
因为经常用来发送异步操作的完成从而常被误解为其本身也是异步的,其实其本身是同步的。emit
函数可能是异步调用的,但是需要注意的是,所有的回调函数都将按照它们注册时的顺序同步执行。
Event Loop
机制概述
Node本身依赖于多个库,libuv
就是其中一个。libuv
负责处理队列与进程中异步事件。
Node还会尽可能的利用操作系统中的功能,因此,诸如写入请求、保持连接等都交给操作系统去处理,例如请求的连接首先会由操作系统处理,只有它们变得可用时node才会介入处理。
你可能了解node中有一个线程池,因此而产生疑问:既然Node都将那些耗时的操作交由操作系统来处理,那么它为何还需要线程池呢? 这是因为操作系统内核并不是对所有的操作都支持异步,在这种情况下,node需要锁定那些在执行内核不支持异步操作的线程,而使用其他线程来事件循环从而不阻塞整个进程。
下图是整个机制的简图:
另外还有两个重要的点在图中难以展示的是:
未完待续。。。。。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。