代码之家  ›  专栏  ›  技术社区  ›  Ralph Caraveo

怎么样节点.js当它仍然在内部依赖线程时,它天生就更快?

  •  274
  • Ralph Caraveo  · 技术社区  · 14 年前

    我刚刚看了以下视频: Introduction to Node.js 但仍然不明白你是如何获得速度优势的。

    主要是Ryan Dahl(节点.js“创造者”说节点.js是基于事件循环的,而不是基于线程的。线程非常昂贵,只能由并发编程专家来使用。

    随后,他展示了节点.js它有一个底层C实现,内部有自己的线程池。显然如此节点.js开发人员永远不会启动自己的线程或直接使用线程池…他们使用异步回调。我就明白这点。

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

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

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

    6 回复  |  直到 12 年前
        1
  •  142
  •   jrtipton    14 年前

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

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

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

    query() 正在运行。(根据Ryan的说法,Apache使用一个线程来满足最初的请求,而nginx在他所说的情况下的性能要优于它,因为它不是。)

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

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

    这基本上是什么节点.js他在做什么。你基本上是在装饰——由于语言和环境的原因,装饰的方式很方便,因此也就是闭包的要点——你的代码是这样一种方式,环境可以很清楚地知道什么时候运行,什么时候运行。那样的话,节点.js不是吗 新的 从某种意义上说,它发明了异步I/O(并不是说有人声称这样做),但它是新的,因为它的表达方式有点不同。

        2
  •  32
  •   nalply Gagan    12 年前

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

    它使用线程是因为:

    1. 这个 O_NONBLOCK option of open() does not work on files .
    2. 有些第三方库不提供非阻塞IO。

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

    硬件方面更糟糕:

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

    也许将来有人会为文件实现O\u NONBLOCK?。。。

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

        3
  •  29
  •   Toby Eggitt    12 年前

    我担心我在这里“做错事”,如果是这样,请删除我,我道歉。特别是,我没有看到我如何创建一些人创建的整洁的小注释。不过,我对这一问题有许多顾虑/看法。

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

    基本上是假的。如果线程正在计算,那么它不是在摆弄拇指,而是在做必要的工作。另一方面,如果它只是在等待IO的完成,那么它就是 使用CPU时间,内核中线程控制基础设施的全部要点是CPU将找到一些有用的事情来做。这里所建议的“摆弄拇指”的唯一方法是创建一个轮询循环,而编写了真正的web服务器代码的人都不足以做到这一点。

    2) “线程是硬的”,只有在数据共享的环境下才有意义。如果你有本质上独立的线程,比如处理独立的web请求时,那么线程就非常简单了,你只需要把如何处理一个作业的线性流编好代码,就知道它将处理多个请求,而且每个请求都是有效独立的。我个人认为,对于大多数程序员来说,学习闭包/回调机制比简单地编写从上到下的线程版本要复杂得多。(但是是的,如果你必须在线程之间通信,生活会变得非常艰难,但是我不相信闭包/回调机制真的改变了这一点,它只是限制了你的选择,因为这种方法仍然可以通过线程实现。不管怎样,这是一个完全不同的讨论,在这里真的不相关)。

    4) 到目前为止,我所看到的所有旨在显示Node比其他web服务器快多少的插图都存在严重的缺陷,然而,它们的缺陷确实间接地说明了Node的一个优势,我肯定会接受(而且这绝不是无关紧要的)。节点看起来不需要(实际上甚至不允许)调优。如果有线程模型,则需要创建足够的线程来处理预期的负载。如果做得不好,你的表现就会很差。如果线程太少,那么CPU是空闲的,但是不能接受更多的请求,创建太多的线程,这样会浪费内核内存,在Java环境中,也会浪费主堆内存。现在,对于Java来说,浪费堆是第一个,也是最好的,破坏系统性能的方法,因为高效的垃圾收集(目前,G1可能会改变这一点,但至少在2013年初,这一点还没有定论)取决于有大量的空闲堆。所以,有一个问题,用太少的线程调整它,你有空闲的CPU和低吞吐量,用太多的线程调整它,它在其他方面陷入困境。

    内核

    a) 关于Node为什么应该更好的一个真正的解释(除了我在上面概述的两个场景之外,我认为第一个场景(糟糕的调优)是到目前为止我看到的所有测试的真正解释。([edit],实际上,我想得越多,我就越想知道大量堆栈使用的内存在这里是否有意义。现代线程的默认堆栈大小往往相当大,但基于闭包的事件系统所分配的内存只是所需的)

    b) 一个真正的基准测试,它实际上为所选择的线程服务器提供了一个公平的机会。至少这样,我就不得不停止相信这些说法基本上是错误的;([编辑]这可能比我预期的要强烈得多,但我确实觉得对绩效福利给出的解释充其量是不完整的,显示的基准是不合理的)。

    干杯, 托比

        4
  •  14
  •   Alfred    14 年前

    我不明白的是 那个节点.js仍在使用线程。

    slide 63(internal design) 你看Ryan用 libev (抽象异步事件通知的库)用于非阻塞 eventloop

        5
  •  11
  •   gawi    14 年前

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

    这个 stat()

        6
  •  7
  •   BGerrissen    14 年前

    我完全不知道节点.js,但我可以看到使用事件循环如何优于线程化I/O处理。想象一个光盘请求,给我staticFile.x,对那个文件做100个请求。每个请求通常占用一个线程来检索该文件,即100个线程。

    一旦单个线程完成,它会将staticFile.x传递给所有100个侦听器并销毁自身,因此下一个请求将创建一个新的线程和发布者对象。

    所以在上面的例子中是100个线程对1个线程,但是也是1个磁盘查找而不是100个磁盘查找,增益可能相当可观。瑞安是个聪明人!

    从另一个角度来看,这是他在电影开头的一个例子。而不是:

    pseudo code:
    result = query('select * from ...');
    

    pseudo code:
    query('select * from ...', function(result){
        // do stuff with result
    });
    

    如果一个查询已经在进行中,那么其他相等的查询也会随波逐流,因此在一个数据库往返中可以有100个查询。