当 Node.js仍然在内部依赖 Threads 时,它本身是如何变得更快的?

我刚刚观看了以下视频:Node简介.js但仍然不明白您如何获得速度优势。

主要是,Ryan Dahl(Node.js的创建者)说Node.js是基于事件循环的,而不是基于线程的。线程很昂贵,应该只留给并发编程专家使用。

稍后,他展示了Node的架构堆栈.js它具有底层C实现,该实现在内部具有自己的Thread池。因此,很明显,Node.js开发人员永远不会启动自己的线程或直接使用线程池......它们使用异步回调。我明白这一点。

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

唯一的区别是,由于它是在 Node 内部管理的.js开发人员不必对线程详细信息进行编码,但在下面,它仍然使用线程来处理 IO(阻塞)文件请求。

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

这里肯定有一些我仍然不明白的细节。


答案 1

实际上,这里将一些不同的东西混为一谈。但它始于模因,即线程真的很难。因此,如果它们很难,那么在使用线程时,您更有可能1)由于错误而中断,2)不尽可能有效地使用它们。(2)是你要问的那个。

想想他给出的一个例子,其中一个请求进来,你运行一些查询,然后用它的结果做一些事情。如果以标准过程方式编写它,则代码可能如下所示:

result = query( "select smurfs from some_mushroom" );
// twiddle fingers
go_do_something_with_result( result );

如果传入的请求导致您创建一个运行上述代码的新线程,您将有一个线程坐在那里,在运行时不执行任何操作。(根据Ryan的说法,Apache正在使用单个线程来满足原始请求,而nginx在他所说的情况下表现优于它,因为它不是。query()

现在,如果你真的很聪明,你可以用一种方式来表达上面的代码,在这种方式中,环境可以在你运行查询时关闭并执行其他操作:

query( statement: "select smurfs from some_mushroom", callback: go_do_something_with_result() );

这基本上就是node.js正在做的事情。你基本上是在装饰你的代码——以一种由于语言和环境的原因而方便的方式,因此关于闭包的要点——你的代码可以这样一种方式,即环境可以聪明地知道什么运行,以及何时运行。通过这种方式,node.js并不新鲜,因为它发明了异步I / O(并不是说有人声称有这样的东西),但它的表达方式有点不同。

注意:当我说环境可以聪明地决定什么和何时运行时,我的意思是它用来启动一些I / O的线程现在可以用来处理一些其他请求,或者一些可以并行完成的计算,或者启动一些其他并行I / O。 但你明白了。


答案 2

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

它使用线程是因为:

  1. open() 的 O_NONBLOCK 选项不适用于文件
  2. 有些第三方库不提供非阻塞 IO。

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

在硬件级别上甚至更糟:

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

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

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

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