线程每个请求模型是否比非阻塞I/O更快?

9
我记得2或3年前读到几篇文章,人们声称现代线程库变得越来越好,以至于每个请求一个线程的服务器不仅比非阻塞服务器更容易编写,而且速度更快。我相信在Java中甚至用一个将Java线程映射到pthread的JVM进行了演示(即Java nio开销大于上下文切换开销)。
但现在我看到所有“前沿”的服务器都使用异步库(如Java nio、epoll,甚至是node.js)。这是否意味着异步获胜了?
4个回答

8
在我看来,如果两种模型都被很好地实现了(这是一个很大的要求),我认为NIO的概念应该占上风。
计算机的核心是核心。无论你做什么,你都不能将应用程序并行化超过核心数量。也就是说,如果你有一台4核的机器,你只能同时执行4个任务(我在这里略过了一些细节,但这足以支持这个论点)。
进一步思考,如果你拥有的线程数多于核心数,那么你就会浪费资源。这种浪费有两种形式。第一是额外线程本身的开销。第二是在线程之间切换所花费的时间。两者可能都很小,但它们确实存在。
理想情况下,每个核心有一个单独的线程,并且这些线程在其核心上以100%的处理速度运行。在理想情况下,不会发生任务切换。当然还有操作系统,但如果你拿出一台16核的机器,留下2-3个线程给操作系统,那么剩下的13-14个线程就可以用于你的应用程序。这些线程可以在应用程序内部进行任务切换,例如当它们被IO需求阻塞时,但不必在操作系统级别支付这种成本。将其正确地编写到您的应用程序中。
这种扩展的一个很好的例子可以在SEDA中看到http://www.eecs.harvard.edu/~mdw/proj/seda/。它在负载下表现出比标准的每请求一个线程模型更好的扩展性。
我的个人经验是使用Netty。我有一个简单的应用程序,在Tomcat和Netty中都实现得很好。然后我进行了100多个并发请求(超过800个),最终Tomcat变得非常缓慢,并表现出极其爆炸/延迟的行为。而Netty的实现仅增加了响应时间,但整体吞吐量仍然非常高。
请注意,这取决于稳定的实现。NIO随着时间的推移仍在不断改进。我们正在学习如何调整服务器操作系统以更好地与之配合,以及如何实现JVM以更好地利用操作系统功能。我认为还不能确定胜者,但我相信NIO将成为最终的胜者,它已经做得非常出色了。

这是一个非常有趣的现实世界测试。对于许多应用程序来说,800个并发请求并不算太多。我想知道Tomcat中的延迟是否由于上下文切换(或GC,如@irreputable所建议的)造成,这意味着每个请求一个线程模型本身存在问题,而不是Tomcat的实现问题。 - Graham
1
公平地说,这是在测试单个服务器而不是集群。至于800个请求,在我的经验中,800并发是相当高的。重要的是要区分并发请求和连接。如果您在该负载下获得100毫秒范围内的响应时间,则支持每秒8k个请求。对于单个框来说,这还不错。将其扩展到整个一天,您可以使用单个服务器处理7亿个请求。没有多少应用程序需要那么多。 - rfeak

5
只要有足够的内存,它就更快了。
当连接数过多且大部分都是空闲的时候,NIO 可以节省线程,从而节省内存,系统可以处理比单个连接模型更多的用户。
CPU 在这里并不是一个直接的因素。使用 NIO,您需要有效地自己实现一个线程模型,这可能不如 JVM 的线程快。
在任何选择中,内存都是最终的瓶颈。当负载增加并且使用的内存接近最大值时,GC 将非常忙碌,并且系统通常表现�� 100% CPU 的症状。

这很有道理。快速的谷歌搜索显示,在64位机器上,HotSpot默认的每个线程堆栈大小为1MB(我知道这不是堆,但它会减少剩余的堆内存量)。仅仅这一点就意味着在Java中,基于线程的请求处理模型并不适合C10K。但如果内存是限制因素,那么它可能适用于数百个并发连接。 - Graham

4

有些时间前,我发现了一份相当有趣的演示文稿,提供了一些关于“为什么旧的每个客户端线程模型更好”的见解。甚至还有一些测量数据。然而,我仍在深思熟虑。在我看来,对于这个问题最好的答案是“因情况而异”,因为大多数(如果不是全部)工程决策都是取舍。


我认为这可能是我记得的演示。这些观察(nio 可能会很慢)加上对 Erlang 和 Scala 中基于 actor 的并发性的新发现,让我认为世界正在朝着同步模型的方向发展。但在那些语言之外(以及 Scala 的大多数欺骗),我认为我会坚持将同步作为默认选项,直到我遇到像 @rfeak 遇到的真实世界问题。 - Graham

1

就像那个演示所说的 - 有速度和可扩展性。

一个场景中,每个请求都有一个线程几乎肯定比任何异步解决方案更快,这种情况是当您有相对较少的客户端(例如<100),但每个客户端的流量非常高时。例如,实时应用程序,其中不超过100个客户端每秒发送/生成500条消息。线程每请求模型肯定比任何异步事件循环解决方案更有效率。异步在有许多请求/客户端时扩展得更好,因为它不会浪费时间等待许多客户端连接,但是当您只有少数高流量客户端且等待时间较短时,它的效率较低。

从我所看到的,Node和Netty的作者都认识到这些框架主要是为了解决高容量/多连接可扩展性问题,而不是针对少数高流量客户端更快。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接