我刚刚看了下面的视频:Node.js介绍,仍然不明白你是如何获得速度优势的。

主要是,Ryan Dahl (Node.js的创造者)说Node.js是基于事件循环的,而不是基于线程的。线程的开销很大,只能留给并发编程专家使用。

随后,他展示了Node.js的架构堆栈,其中有一个底层C实现,在内部有自己的线程池。所以很明显Node.js开发者永远不会启动他们自己的线程或者直接使用线程池…它们使用异步回调。这点我能理解。

我不明白的是Node.js仍然在使用线程…它只是隐藏的实现,所以这是如何更快,如果50人请求50个文件(目前不在内存中),那么不需要50个线程?

唯一的区别是,由于它是内部管理的,Node.js开发人员不必编写线程细节,但在底层它仍然使用线程来处理IO(阻塞)文件请求。

所以你真的只是把一个问题(线程)隐藏起来,而这个问题仍然存在:主要是多线程,上下文切换,死锁……等等?

这里一定有一些细节我还是不明白。


当前回答

我不明白的是重点 Node.js仍然在使用线程。

Ryan使用线程处理阻塞的部分(大多数node.js使用非阻塞IO),因为有些部分很难编写非阻塞。但我相信瑞恩的愿望是一切都不阻塞。 在第63页(内部设计)中,您可以看到Ryan使用libev(抽象异步事件通知的库)来实现非阻塞事件循环。由于事件循环,node.js需要更少的线程,从而减少上下文切换,内存消耗等。

其他回答

我对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.JS并不更快(并不意味着它更慢),但在处理单个线程时非常高效,与处理单个线程的阻塞多线程系统相比!

我已经做了图表,用类比来解释这句话。

现在当然可以在阻塞多线程系统(这就是Node.js的本质)之上构建一个非阻塞系统,但它非常复杂。你必须在任何需要非阻塞代码的地方这样做。

Javascript生态系统(如nodejs)提供了开箱即用的语法。JS语言sytanx在任何需要的地方都提供了所有这些特性。此外,作为语法的一部分,代码的读者可以立即知道代码在哪里阻塞,在哪里非阻塞。


多线程阻塞系统的阻塞部分降低了系统的效率。被阻塞的线程在等待响应期间不能用于其他任何事情。

而非阻塞单线程系统则充分利用了它的单线程系统。

我不明白的是重点 Node.js仍然在使用线程。

Ryan使用线程处理阻塞的部分(大多数node.js使用非阻塞IO),因为有些部分很难编写非阻塞。但我相信瑞恩的愿望是一切都不阻塞。 在第63页(内部设计)中,您可以看到Ryan使用libev(抽象异步事件通知的库)来实现非阻塞事件循环。由于事件循环,node.js需要更少的线程,从而减少上下文切换,内存消耗等。

注意!这是一个古老的答案。虽然在大致轮廓中仍然如此,但由于Node在过去几年的快速发展,一些细节可能已经发生了变化。

它使用线程是因为:

open()的O_NONBLOCK选项对文件不起作用。 有些第三方库不提供非阻塞IO。

要伪造非阻塞IO,线程是必要的:在单独的线程中执行阻塞IO。这是一种丑陋的解决方案,会导致大量开销。

硬件层面的情况更糟:

使用DMA, CPU异步卸载IO。 数据直接在IO设备和内存之间传输。 内核将其封装在一个同步的、阻塞的系统调用中。 Node.js将阻塞系统调用包装在一个线程中。

这是愚蠢和低效的。但至少它是有效的!我们可以享受Node.js,因为它隐藏了事件驱动的异步架构背后丑陋和繁琐的细节。

也许将来有人会为文件实现O_NONBLOCK ?…

编辑:我和一个朋友讨论过这个问题,他告诉我线程的另一种选择是使用select轮询:指定一个超时为0,并对返回的文件描述符执行IO(现在它们被保证不会阻塞)。

线程仅用于处理没有异步功能的函数,如stat()。

stat()函数总是阻塞的,所以node.js需要使用一个线程来执行实际调用,而不阻塞主线程(事件循环)。如果不需要调用这类函数,那么线程池中的线程可能永远不会被使用。