accept 为什么不阻塞_为什么不阻塞?

accept 为什么不阻塞

在过去的一年中,我一直在编写无阻塞的异步代码。 学习它的工作原理和编写方法并不难。 从哪里来的好处是我不了解的。 此外,围绕某些编程模型的炒作太多,以至于您必须善于从事实的谣言中告诉市场。

因此,让我们首先从澄清术语开始。 非阻塞应用程序以线程永远不会阻塞的方式编写–每当线程必须在I / O上阻塞(例如,从套接字读取/写入套接字)时,它都会在有新数据可用时得到通知。 如何实现这一点超出了本文的范围。 非阻塞应用程序通常通过消息传递(或事件)来实现。 “异步”与此相关(实际上,在许多情况下,它是“非阻塞”同义词 ),因为您发送请求事件,然后在不同的时间,异步地在不同的线程中获得对它们的响应。 然后是“React式”流行语,我实在无法解释-一方面是React式函数式编程 ,它相当抽象。 另一方面,有一个React式宣言 ,它为几乎每个应用程序定义了3个要求(响应,弹性,有弹性)和一个实现细节(消息驱动),这没有明显的原因。 整个过程与非阻塞/异步编程有何关系-可能是由于消息驱动的原因,但通常这三者以流行语驱动的行销术语并存。

用于实现非阻塞(Web)应用程序的框架/工具的两个示例是Akka(针对Scala nad Java)和Node.js。 我一直在使用前者,但是大多数事情也与Node有关。

这是它的工作原理的简化描述。 它使用React堆模式 (啊,也许是“React”的源头?),其中一个线程通过在任务之间进行复用来处理所有请求,并且从不在任何地方阻塞–只要准备就绪,它就会由该线程(或几个线程)进行处理。 )。 因此,如果对从数据库读取并写入响应的Web应用程序发出了两个请求,则框架会从每个套接字读取输入(通过获取传入数据的通知,在两个套接字之间进行切换),以及何时读取所有内容,将“这是请求”消息传递给应用程序代码。 然后,应用程序代码将消息发送到数据库访问层,后者又将消息发送到数据库(驱动程序),并在从数据库中读取数据完成时得到通知。 在回调中,它依次向前端/控制器发送消息,前端/控制器通过将消息作为消息发送,从而将数据写入响应。 一切都包括大量消息传递以及可能的回调。

这种设置的一个问题是,如果代码在代码中的任何一点被阻塞,那么整个事情就会陷入困境。 但是,让我们假设您的所有代码和第三方库都是非阻塞的,并且/或者您有一些聪明的方法来避免阻塞所有内容(例如,处理阻塞部分的内部线程池)。

这又引出了另一点–是否只读写套接字是非阻塞的,而不是整个应用程序都是非阻塞的。 例如,Tomcat的NIO连接器是非阻塞的,但是(通过线程池通过afaik)应用程序代码可以以“旧的”同步方式执行。 尽管我承认我没有完全理解这一部分 ,但是我们必须将异步应用程序代码与基础结构提供的异步I / O区分开。

还有另一个重要的区别–服务器代码是非阻塞/异步的,这并不意味着您的应用程序与客户端是异步的。 两者是相关的,但并不相同–如果您的客户端使用长期连接,并且希望从服务器推送新数据(例如,websockets / comet),那么异步性将超出代码范围,并成为应用程序的功能,从客户的角度来看。 这可以通过多种方式来实现,包括使用async = true的Java Servlet(即使用非阻塞模型,以便长寿命连接不会每个都持有阻塞线程)。

好的,现在我们大致了解了它的工作原理,我们甚至可以使用该范例编写代码。 我们可以传递消息,编写回调或通过其他消息获得通知(即akka的“询问”与“告诉”模式)。 但是,再次-有什么意义?

那就是棘手的地方。 您可以尝试谷歌搜索诸如“无阻塞/ NIO的优点”,基准测试,“更快的东西–阻塞或非阻塞”之类的内容。人们会说,无阻塞需要更快,或更可扩展性用于线程的内存更少,具有更高的吞吐量或这些的任意组合。 是真的吗 没人知道。 确实有道理,通过不阻塞线程,并且当您没有每个套接字的线程时,可以让更少的线程服务更多的请求。 但这是更快或更有效的内存吗? 在最大化CPU,网络I / O或磁盘I / O之前,您是否达到了大线程池中的最大线程数? 常规Web应用程序中的瓶颈真的是线程池吗? 可能 ,但我找不到确切的答案。

该基准测试表明原始servlet比Node更快(并且当在该benechmark中出现spray(akka)时,它也较慢)。 表明NIO tomcat连接器的吞吐量较差。 我自己的Spray与spring-mvc基准测试(我输了)显示,Spray开始返回500(内部服务器错误)响应,并发请求的方式比spring-mvc少。 我敢打赌,否则会有反基准“证明”。

关于该主题的最全面的文章是2008年的“成千上万的线程和阻塞I / O”演示 ,我本人感觉到–每个人“知道”非阻塞会越来越好,但没有人实际测试过,并且人们有时会混淆“快速”和“可扩展”。 而且,阻塞服务器的执行速度实际上要快20倍。 该演讲辅以“避免NIO”一文 ,声称无阻塞方法实际上在可伸缩性和性能方面更差。 这篇论文 (来自2003年)声称“事件是个坏主意(对于高并发服务器)”。 但是,所有这些目标是不是仅对Java NIO库或一般的非阻塞方法成立? 它是否适用于Node.js和akka / spray,以及从客户端角度来看异步的应用程序如何适应图片–老实说,我不知道。

感觉好像旧的基于线程池的阻塞方法至少足够好,甚至更好。 尽管“常识”不是。

为了使事情更加复杂,让我们考虑用例。 也许您应该对具有传统请求/响应范例的RESTful API使用阻塞方法,但是由于异步特性,也许应该使高速交易Web应用程序成为非阻塞方法。 您是否应该仅使您的“连接器”(用tomcat术语表示)非阻塞,而其余的应用程序却阻塞……除了异步(从客户端角度而言)部分之外? 回答起来真的很复杂。

甚至“取决于”也不是一个足够好的答案。 有人会说,对于您的用例,您应该以自己的基准进行测试。 但是对于基准测试,您需要一个实际的应用程序。 以各种可能的方式编写。 是的,您可以使用一些原型的基本功能,但是选择编程范例必须很早就进行( 以后很难对其进行重构 )。 那么,哪种方法性能更高,可扩展性更高,内存效率更高? 我不知道。

但是,我所知道的是,这更容易编程,更易于测试并且更易于支持。 这就是阻碍范式。 在这里,您可以简化对象的调用方法,而不必关心回调和处理响应。 同步,简单,直接。 这实际上是演示文稿和我上面链接的论文中的要点之一-编写非阻塞代码更加困难。 鉴于收益尚不清楚(如果有的话),我要说编程,测试和支持代码是主要的区别特征。 您是否能够从一台计算机上服务10000个或11000个并发用户并不重要。 硬件便宜。 (当然,除非是1000对10000)。

但是,为什么非阻塞,异步,事件/消息驱动的编程范例更难? 至少对我来说,即使在该范式编写了一年之后,它仍然更加混乱。 首先,追踪编程流程更加困难。 使用同步代码,您只需告诉您的IDE来获取调用层次结构(或者,如果您的语言不兼容IDE,则查找给定方法的用法),并查看一切进展。 对于事件而言,这并非微不足道。 谁构造此消息? 它在哪里发送给/谁消费? 如何获得响应-通过回调,通过另一条消息? 响应消息何时构造,谁真正使用它? 不,这不是“松散耦合”,因为您的代码仍然在逻辑上(和编译方式)上是耦合的,因此很难阅读。

线程安全又如何?据称,通过事件可以确保不发生争用,死锁或竞争条件。 好吧,即使那不一定是真的。 您必须非常小心回调(除非您确实像Node中那样有一个线程)和“ actor”状态。 哪个线程执行哪个代码很重要(至少在akka中很重要),即使只有几个线程可以完成工作,您仍然可以处于共享状态。 对于同步方法,您只需要遵循一个简单的规则– 状态不属于代码 ,周期。 没有实例变量,并且无论有多少线程执行同一段代码,您都是安全的。 上面的演示还提到了不可变的并发数据结构,它们本质上是线程安全的,并且可以在两种范式中使用。 因此,就并发而言,从开发人员的角度来看,这非常容易。

测试复杂的消息传递流确实是一场噩梦。 虽然测试代码通常比生产代码可读性差,但以我的经验,非阻塞应用程序的测试代码要丑陋得多。 但这又是主观的,我同意。

我不想用“视情况而定”来完成这一漫长而漫无目的的文章。 我确实认为,具有线程池并且没有在业务逻辑中传递消息的同步/阻塞编程模型是编写代码的更简单,更直接的方法。 并且,如演示文稿和所链接的论文所指出的那样,它也更快-很棒。 当您确实需要异步地将响应发送到客户端时,请考虑仅针对该功能的这一部分采用非阻塞方法。 最终,考虑到类似的性能,吞吐量,可伸缩性(并且忽略了营销嗡嗡声),我认为应该选择更易于编写,阅读和测试的编程范例。 由于启动另一台服务器需要30分钟,因此意外的复杂性可能会耗费数周甚至数月的编程时间。 对我而言,阻塞/同步方法更容易编写,读取和测试,但这不一定通用。 我只是不会根据对性能和可伸缩性的模糊主张来选择编程范例。

翻译自: https://www.javacodegeeks.com/2015/03/why-non-blocking.html

accept 为什么不阻塞

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值