我刚刚看了下面的视频:Node.js介绍,仍然不明白你是如何获得速度优势的。
主要是,Ryan Dahl (Node.js的创造者)说Node.js是基于事件循环的,而不是基于线程的。线程的开销很大,只能留给并发编程专家使用。
随后,他展示了Node.js的架构堆栈,其中有一个底层C实现,在内部有自己的线程池。所以很明显Node.js开发者永远不会启动他们自己的线程或者直接使用线程池…它们使用异步回调。这点我能理解。
我不明白的是Node.js仍然在使用线程…它只是隐藏的实现,所以这是如何更快,如果50人请求50个文件(目前不在内存中),那么不需要50个线程?
唯一的区别是,由于它是内部管理的,Node.js开发人员不必编写线程细节,但在底层它仍然使用线程来处理IO(阻塞)文件请求。
所以你真的只是把一个问题(线程)隐藏起来,而这个问题仍然存在:主要是多线程,上下文切换,死锁……等等?
这里一定有一些细节我还是不明白。
我对node.js的内部工作原理一无所知,但我可以看到如何使用事件循环可以胜过线程I/O处理。想象一个磁盘请求,给我一个staticFile。X,为该文件设置100个请求。每个请求通常占用一个线程来检索该文件,也就是100个线程。
现在想象一下,第一个请求创建了一个线程,该线程成为一个发布者对象,所有其他99个请求首先查看staticFile是否有一个发布者对象。X,如果是,在它工作时监听它,否则启动一个新线程,从而创建一个新的publisher对象。
一旦单个线程完成,它将传递staticFile。X发送给所有100个侦听器,并销毁自己,因此下一个请求创建一个新的线程和发布者对象。
因此,在上面的例子中,它是100个线程vs 1个线程,但也是1个磁盘查找而不是100个磁盘查找,增益是非常显著的。瑞恩是个聪明人!
另一种方法是看他在电影开头的一个例子。而不是:
pseudo code:
result = query('select * from ...');
同样,100个独立的数据库查询与…
pseudo code:
query('select * from ...', function(result){
// do stuff with result
});
如果一个查询已经在运行,其他相同的查询将简单地跟随潮流,因此在单个数据库往返中可以有100个查询。
注意!这是一个古老的答案。虽然在大致轮廓中仍然如此,但由于Node在过去几年的快速发展,一些细节可能已经发生了变化。
它使用线程是因为:
open()的O_NONBLOCK选项对文件不起作用。
有些第三方库不提供非阻塞IO。
要伪造非阻塞IO,线程是必要的:在单独的线程中执行阻塞IO。这是一种丑陋的解决方案,会导致大量开销。
硬件层面的情况更糟:
使用DMA, CPU异步卸载IO。
数据直接在IO设备和内存之间传输。
内核将其封装在一个同步的、阻塞的系统调用中。
Node.js将阻塞系统调用包装在一个线程中。
这是愚蠢和低效的。但至少它是有效的!我们可以享受Node.js,因为它隐藏了事件驱动的异步架构背后丑陋和繁琐的细节。
也许将来有人会为文件实现O_NONBLOCK ?…
编辑:我和一个朋友讨论过这个问题,他告诉我线程的另一种选择是使用select轮询:指定一个超时为0,并对返回的文件描述符执行IO(现在它们被保证不会阻塞)。