首页 文章

Node.js在内部依赖Threads时本身如何更快?

提问于
浏览
270

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

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

之后,他展示了Node.js的体系结构堆栈,它有一个底层的C实现,内部有自己的Thread池 . 显然,Node.js开发人员永远不会启动他们自己的线程或直接使用线程池...他们使用异步回调 . 我明白了 .

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

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

那么你是不是真的只是遇到一个问题(线程)并在问题仍然存在的时候隐藏它:主要是多线程,上下文切换,死锁......等等?

必须有一些我仍然不明白的细节 .

6 回答

  • 13

    实际上有一些不同的东西在这里被混淆 . 但它始于线程真的很难的模因 . 因此,如果它们很难,则更有可能在使用线程时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() );
    

    这基本上就是node.js正在做的事情 . 你发明了异步I / O(不是说有人声称这样的东西)这个意义上的新东西,但它表达的有点不同 .

    注意:当我说环境可以巧妙地关于什么运行和什么时候,具体我的意思是它用来启动一些I / O的线程现在可以用来处理一些其他请求,或者可以做一些计算并行,或启动一些其他并行I / O. (我不确定节点是否足够复杂,可以为同一个请求开始更多的工作,但是你明白了 . )

  • 11

    Note! 这是一个老答案 . 虽然它在过去几年中发展迅速 .

    它正在使用线程,因为:

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

    它在硬件级别上更糟糕:

    • 使用DMA CPU异步卸载IO .

    • 数据直接在IO设备和内存之间传输 .

    • 内核在同步阻塞系统调用中包装它 .

    • Node.js将阻塞系统调用包装在一个线程中 .

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

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

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

  • 28

    我担心我在这里做“做错事”,如果是这样,请删除我,我道歉 . 特别是,我没有看到我如何创建一些人创建的整洁的小注释 . 但是,我在这个帖子上有很多关注/意见 .

    1)在一个流行的答案中的伪代码中的注释元素

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

    基本上是虚假的 . 如果线程正在计算,那么's not twiddling thumbs, it'正在做必要的工作 . 另一方面,如果它没有使用CPU时间,那么内核中线程控制基础结构的重点就是CPU会找到一些有用的东西 . 这里建议的唯一方法是创建一个轮询循环,没有人编写真正的网络服务器就不足以做到这一点 .

    2)“线程很难”,只在数据上下文中才有意义分享 . 如果您具有基本上独立的线程,例如处理独立Web请求时的情况,那么线程很简单,您只需编写如何处理一个作业的线性流程,并且非常知道它将处理多个请求,并且每个将是有效独立的 . 就个人而言,我敢说,对于大多数程序员来说,学习闭包/回调机制比简单编写从上到下的线程版本更复杂 . (但是,是的,如果你必须在线程之间进行通信,生活变得非常困难,但是后来我不相信闭包/回调机制真的改变了它,它只是限制你的选择,因为这种方法仍然可以通过线程实现无论如何,这是另一个与此无关的讨论 .

    3)到目前为止,没有人提供任何真实证据表明为什么一种特定类型的上下文切换比任何其他类型更多或更少时间 . 我在创建多任务内核方面的经验(在嵌入式控制器的小规模上,没有像“真正的”操作系统那么花哨)表明情况并非如此 .

    4)迄今为止我所看到的所有插图都表明Node比其他网络服务器的速度快得多,但是,它们的缺陷确实间接地说明了我一定会接受Node的一个优势(和这绝不是微不足道的) . 节点看起来不像它需要(甚至不允许,实际上)调整 . 如果您有线程模型,则需要创建足够的线程来处理预期的负载 . 这样做很糟糕,你最终会表现不佳 . 如果线程太少,那么CPU处于空闲状态,但无法接受更多请求,创建太多线程,并且您将浪费内核内存,而在Java环境中,您也将浪费主堆内存 . 现在,对于Java来说,浪费堆是第一个,最好的方式来搞砸系统的性能,因为有效的垃圾收集(目前,这可能会随着G1的变化而变化,但似乎陪审团仍然在2013年初就出现了问题至少)取决于拥有大量备用堆 . 所以,有问题,用太少的线程调整它,你有空闲的CPU和糟糕的吞吐量,用太多的调整它,并且它以其他方式陷入困境 .

    5)还有另一种方式我接受Node 's approach 1016138 , and that is this. Most thread models use a time-sliced context switch model, layered on top of the more appropriate (value judgement alert :) and more efficient (not a value judgement) preemptive model. This happens for two reasons, first, most programmers don' t似乎理解优先级抢占的声明的逻辑,其次,如果你在windows环境中学习线程,那么无论你喜欢与否,时间都在那里(当然这强调了第一点;值得注意的是,Java的第一个版本在Solaris实现上使用了优先级抢占,在Windows中使用了timeslicing . 因为大多数程序员都没有理由认为在简单的Web服务器中就是这种情况 . 所以,是的,时间片中涉及的多余上下文切换是低效的(并且这些通常不会发生在内核线程中,顺便说一句)但差异将是吞吐量的百分之几,而不是隐含的整数因子的种类 . 在性能声明中经常暗示Node .

    无论如何,为这一切都很漫长而轻率道歉,但我真的觉得到目前为止,讨论还没有证明什么,我很高兴听到有人在以下任何一种情况下:

    a)真正解释为什么Node应该更好(除了我上面概述的两个场景之外,我认为第一个(调整不佳)是我到目前为止所见过的所有测试的真实解释 . ([编辑]实际上,我越是想到它,我就越想知道大量堆栈使用的内存是否在这里很重要 . 现代线程的默认堆栈大小往往相当大,但是由一个分配的内存基于闭包的事件系统将只是需要的东西)

    b)真正的基准测试,实际上为所选择的线程服务器提供了公平的机会 . 至少就是这样,我不得不停止相信这些说法基本上是假的;>([编辑]这可能比我想要的更强,但我确实认为对性能优势给出的解释充其量是不完整的,而且显示的基准是不合理的) .

    干杯,托比

  • 32

    我不明白的是Node.js仍在使用线程 .

    Ryan对阻塞的部分使用线程(大多数node.js使用非阻塞IO)因为某些部分很难编写非阻塞 . 但我相信瑞安希望一切都是无阻碍的 . 在slide 63(internal design)上,您看到Ryan使用libev(抽象异步事件通知的库)进行非阻塞eventloop . 由于事件循环node.js需要较少的线程,这减少了上下文切换,内存消耗等 .

  • 136

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

    stat() 函数始终是阻塞的,因此node.js需要使用线程来执行实际调用而不会阻塞主线程(事件循环) . 如果您不需要调用这些函数,则可能不会使用线程池中的任何线程 .

  • 7

    我对node.js的内部工作原理一无所知,但我可以看到使用事件循环可以胜过线程I / O处理 . 想象一下光盘请求,给我staticFile.x,对它提出100个请求 . 每个请求通常占用一个线程检索该文件,即100个线程 .

    现在假设第一个请求创建一个成为发布者对象的线程,所有99个其他请求首先查看是否有staticFile.x的发布者对象,如果是,请在它正在进行工作时听取它,否则启动一个新线程,从而新的发布者对象 .

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

    因此,在上面的示例中,它是100个线程与1个线程,但也是1个磁盘查找而不是100个磁盘查找,增益可能非常明显 . 瑞恩是个聪明人!

    另一种看待的方式是他在电影开头的一个例子 . 代替:

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

    同样,对数据库的100个单独查询与...:

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

    如果一个查询已经开始,其他相同的查询只会跳转到这个行列,所以你可以在单个数据库往返中有100个查询 .

相关问题