第一个基本论点:IO是昂贵的。
看图:
所以,当前编程技术的最大浪费,是等待IO完成。
解决这个性能效应有几个方法(来自sam rushing)
- 同步:按照次序,一次处理一个请求。好处:简单,坏处:任何人的一个请求都会阻塞其他全部人的请求
- 建一个新进程:一个进程处理一个请求。好处:容易 坏处:不具备可伸缩性,几百个链接意味着几百个进程
- 线程:一个线程处理一个请求。好处:容易。相比创建进程来说对内核来说开销低。坏处:你的机器未必支持线程。线程开发会很快的编程复杂,需要为共享资源的访问忧心。
第二个基本论点:每个连接一个线程的做法会过度消费内存。
Apache就是多线程的,它对每个请求启动一个线程(或者进程,根据配置)。你可以看到,当并行连接增长,更多线程创建以便服务于并行访问的客户时,这样的开销如何吃掉更多的内存。
Nginx和Node.js 是单线程的,基于事件的。通过一个线程处理多个连接的方法,它根除了创建数以千计的线程或者进程的开销.
事实:Node.js 在你的代码内保持一个线程。。。
确实如此。你不能做任何并行代码执行;如果你写一个sleep函数,它会阻塞整个服务器一秒钟:
while(new Date().getTime() < now + 1000) { // do nothing }
在这段代码运行期间,node.js 不会响应任何来自客户的其他的请求,因为它只有一个线程执行你的代码。或者说,如果你有一个CPU计算密集的代码,好比说是缩放图片,它也会阻塞所以其他的请求。这样说来,每件事情都是并行的,除了你的代码。
没有办法让代码在一个请求内并行起来。当然,全部的IO都是异步的和事件化的,因此,下面的代码不会阻塞服务器:
c.query(
'SELECT SLEEP(20);',
function (err, results, fields) {
if (err) {
throw err;
}
res.writeHead(200, {'Content-Type': 'text/html'});
res.end('Return from async DB query;');
c.end();
}
);
如果你在一个请求内运行它,其他的请求可以很好的处理,即使在数据库(的某个线程)处于睡眠状态。
为何这样很好?何时我们应该离开同步编码,走向异步和并发呢?
node.js 场景下,你不应该担心后端发生了什么。在做IO操作时,只管使用callback;你可以被担保你的代码从来不会中断,执行IO也从来不会阻塞其他请求,无需担心引发每个请求都需要一个线程或者进程而导致的内存成本。
有异步IO是非常好的。因为IO是昂贵的,我们应该在等待IO期间做带你什么,比如服务其他客户的请求。
方法:事件循环来救驾
事件循环是“一个实体,用来处理进程外部事件,转换它们到callback函数)。
因此在IO调用发生处,其实也是一个转换点。在这个转化点,Node.js 可以从一个正在服务的请求中切换到另外一个请求的服务。在IO操作时间,你的代码保存callback,然后返回控制到node.js 运行时环境。这个callback会在IO完成后被调用。
当前,在后台,有很多线程或者进程去做数据库访问和进程执行。只不过,这个执行体不会显示的暴露给你的代码。你不必关心它们的存在,而只要知道从每个请求(request)的角度看,IO操作是异步的,这些线程或者进程的操作结果会通过事件循环返回到你的代码。和apache的模型相比,可以有更好的线程以及线程开销,无需为每个连接都创建一个线程
除了io调用,node希望每个请求都尽快返回。计算密集的应用应该分离到其他进程,或者webworker去完成。显然这样也就意味着没有其他背景线程的话,,并通过事件循环和这个线程交互的话,你不能并行你的代码。基本上所有发射事件的对象都支持异步事件交互,可以以此方式和被阻塞的代码交互,比如文件,socket,子进程,都是EventEmittger的实例。
内部实现
内部实现,依赖于libev提供的事件循环。有libeio补充实现线程池。想要进一步的学习的话,需要找些libev 的文档来看。
在Node.js 内如何做同步?
Tim Caswell 在它的PPT内展示了他的套路:
函数是第一等的。比如我们传递函数作为数据,排列它们,当需要时,执行它们。
函数的组合。在事件发生,匿名函数或者闭包被执行。
原文:
Understanding the node.js event loop - http://blog.mixu.net/2011/02/01/understanding-the-node-js-event-loop/
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。