在了解node.js之前你首先需要了解的一个基本的论点是:I/O是“昂贵”的。
因此对于当前的编程技术而言,最大的浪费来自于等待I/O的完成。下面列出了改善该问题的几种方式,其中的某个可以帮助你提高性能:
同步:在某一时刻,一次只处理一个请求。但这种情况下,任何一个请求都会“耽误”(阻塞)所有其他的请求。
fork一个新进程:对于每个请求,你启动一个新的进程来处理。这种情况下,无法达到很好的扩展,上百个连接就意味着上百个进程的存在。fork()函数是Unix程序员的锤子,因为使用它很方便,所以每个程序都看起来像个钉子一样(都喜欢用锤子拿来敲敲它)。所以,经常造成过度使用,而有些过往矫正。
线程:开启一个新的线程来处理每个请求。这种方式很简单,并且对于内核来讲使用线程也比fork进程来得“亲切”,因为通常线程花费比进程更少的开销。缺点:你的机子可能不支持基于线程编程,并且基于线程的程序,其复杂度增长得非常快,同时你还会有对访问共享资源的担忧。
你需要了解的第二个论点是:被线程处理的每个连接都是“内存昂贵的”。
Apache是采用多线程处理请求的。它对于每个请求“孵化”出一个线程(或者进程,这取决于配置)来处理。你将会看到随着并发连接数的增长以及更多的线程需要服务多个客户端时,那些开销有多消耗内存。Nginx跟Node.js都不是基于多线程模型的,因为线程跟进程都需要非常大的内存开销。他们都是单线程的,但是基于事件的。这种基于单线程的模型消除了为了处理很多请求而创建成百上千个线程或进程带来的开销。
它确实是基于单线程运行的,你无法编写任何代码来执行并发;例如执行一个"sleep"操作将阻塞整个服务器1秒钟。
因此,当代码运行的时候,node.js将不会响应来自客户端的其他请求,因为它只有一个线程来执行你的代码。或者,如果你有某些CPU密集型的操作,比如说,重置图片的尺寸,那也将阻塞所有其他的请求。
在一个单独的请求里,没有办法可以使得代码并行执行。然而,所有的I/O都是基于时间的并且是异步的,所以接下来的代码将不会阻塞服务器:
如果你在一个请求中这么做,其他请求能够很好得被执行。
采用同步执行是个不错的方式,因为它使得编码变得容易(对比线程而言,并发问题常常让你陷入万劫不复)。
在node.js中,你不需要去担心你的代码在后端会发生。你只需要在你做I/O操作的时候使用回调就可以了。你会得到保证:你的代码不会被中断,并且I/O操作也不会阻塞其他请求(因为没有了那些线程/进程需要花费的开销,比如在Apache中会发生的内存过高等)。
采用异步I/O也很好,因为I/O比那些执行其他操作更昂贵,我们应该做一些更有意义的事情而不是去等待I/O。
一个事件循环指的是——一个实体,它可以处理外部事件并且将它们转化为回调的执行。因此,I/O调用变成了node.js可以从一个请求切换到另外一个请求的“点”,你的代码保存了回调并返回控制权给node.js运行时环境。而回调在最终获得了数据之后被执行。
First-classfunction:例如我们将function作为数据传递,包裹他们以在需要的时候执行。
Function组装:就像你了解的关于异步函数或者闭包一样,在触发了I/O事件之后执行。
回调计数器:对于基于事件的回调,你无法保证对于任何特殊的命令,I/O事件都会被执行。所以,一旦你需要多次查询来完成某个处理,通常你仅需要对任何的并发I/O操作进行计数,然后在你确实需要最后的结果的时候检查是否必要的操作都已全部完成(其中的一个例子是在事件回调中,通过对返回的数据库查询进行计数)。查询会被并发执行,并且I/O也对此提供支持(例如可以通过连接池的方式实现并发查询)。
事件循环:上面已经提到过,你可以将blockingcode包裹进一个基于事件的抽象中去(比如通过运行一个子进程,然后当它执行完成之后再返回)。
真的非常简单!
再次申明原文出处:http://blog.mixu.net/2011/02/01/understanding-the-node-js-event-loop/
另外,转载本文请著名“原文出处”,谢谢!
原文发布时间为:2013-09-28
本文作者:vinoYang