日期:2014-05-16 浏览次数:20385 次
一篇讲解node.js事件循环的文章。原文出处:点击跳转!
在了解node.js之前你首先需要了解的一个基本的论点是:I/O是“昂贵”的。
因此对于当前的编程技术而言,最大的浪费来自于等待I/O的完成。下面列出了改善该问题的几种方式,其中的某个可以帮助你提高性能:
你需要了解的第二个论点是:被线程处理的每个连接都是“内存昂贵的”。
Apache是采用多线程处理请求的。它对于每个请求“孵化”出一个线程(或者进程,这取决于配置)来处理。你将会看到随着并发连接数的增长以及更多的线程需要服务多个客户端时,那些开销有多消耗内存。Nginx跟Node.js都不是基于多线程模型的,因为线程跟进程都需要非常大的内存开销。他们都是单线程的,但是基于事件的。这种基于单线程的模型消除了为了处理很多请求而创建成百上千个线程或进程带来的开销。
它确实是基于单线程运行的,你无法编写任何代码来执行并发;例如执行一个"sleep"操作将阻塞整个服务器1秒钟。
while(new Date().getTime() < now + 1000) { // do nothing }
因此,当代码运行的时候,node.js将不会响应来自客户端的其他请求,因为它只有一个线程来执行你的代码。或者,如果你有某些CPU密集型的操作,比如说,重置图片的尺寸,那也将阻塞所有其他的请求。
在一个单独的请求里,没有办法可以使得代码并行执行。然而,所有的I/O都是基于时间的并且是异步的,所以接下来的代码将不会阻塞服务器:
c.query( 'SELECT SLEEP(20);', function (err, results, fields) { if (err) { throw err; } res.writeHead(200, {'Content-Type': 'text/html'}); res.end('<html><head><title>Hello</title></head><body><h1>Return from async DB query</h1></body></html>'); c.end(); } );
如果你在一个请求中这么做,其他请求能够很好得被执行。
采用同步执行是个不错的方式,因为它使得编码变得容易(对比线程而言,并发问题常常让你陷入万劫不复)。
在node.js中,你不需要去担心你的代码在后端会发生。你只需要在你做I/O操作的时候使用回调就可以了。你会得到保证:你的代码不会被中断,并且I/O操作也不会阻塞其他请求(因为没有了那些线程/进程需要花费的开销,比如在Apache中会发生的内存过高等)。
采用异步I/O也很好,因为I/O比那些执行其他操作更昂贵,我们应该做一些更有意义的事情而不是去等待I/O。一个事件循环指的是——一个实体,它可以处理外部事件并且将它们转化为回调的执行。因此,I/O调用变成了node.js可以从一个请求切换到另外一个请求的“点”,你的代码保存了回调并返回控制权给node.js运行时环境。而回调在最终获得了数据之后被执行。
当然,在node.js内部,仍然是依靠线程和进程来进行数据访问、处理其他任务执行。然而,这些都没有明确地对你的代码暴露出来,所以你不需要额外担心内部如何处理I/O之间的交互。对比Apache的模型,它少去了很多线程以及线程开销,因为对每个连接来讲单独的线程不是必须的。仅仅是当你绝对需要让某个操作并发执行才会需要线程,但即便如此线程也是node.js自己管理的。
除了I/O调用之外,node.js期待所有的请求最好快速返回。比如,那些CPU密集型的工作应该被隔离到另一个进程上去执行(通过与事件交互或者使用像WebWorker一样的抽象)。这很明显意味着当你与事件交互的时候,如果没有另一个线程在后端(node.js运行时),那么你是无法并行化执行代码的。基本上,所有可以emit事件的对象(例如EventEmitter的实例)都支持基于事件的异步交互并且你也可以与“blocking code”交互(例如使用文件、sockets或者在node.js中是EventEmitter的子进程)。使用这种方案的话,就能够很好得利用多核的优势了,可以看看:node-http-proxy。在内部,node.js依赖于libev提供的事件循环,libeio是对于libev的补充,node.js使用池化的线程来提供对于异步I/O的支持。如果你想了解更多细节,你可以看一下libev的文档。
Tim Caswell在其PPT中描述了整个模式:
再次申明原文出处:http://blog.mixu.net/2011/02/01/understanding-the-node-js-event-loop/
另外,转载本文请著名“原文出处”,谢谢!