异步模式在处理客户端请求时为什么比同步模式更好?

4

我有一个客户端-服务器项目,正在寻找更好的处理客户端请求的方法。一些人建议异步模式比同步模式和线程池模式更好。
我的问题是为什么?异步模式有什么缺点吗?

6个回答

7
是的,异步请求通常可以在不使用线程的情况下处理。操作系统对它们有特殊的支持,例如重叠 I/O 和完成端口。它们实质上是利用了内核线程的开销,一种无论如何都需要的线程,因为驱动程序需要能够处理来自多个用户模式程序的多个请求。.NET框架在BeginXxx()方法中很容易地利用了这一点。
使用线程池线程也很便宜,但你要遵守线程池调度器的行为。它并不喜欢启动超过核心数的线程池线程。线程池线程永远不应该用于可能会阻塞一段时间的代码,这对于像建立连接这样的 CS 任务非常典型。
异步代码的错误处理非常困难。当EndXxxx()方法引发异常时,你通常几乎没有上下文。它发生在回调线程上,离主要逻辑非常遥远。当你能够无所谓“没发生”,只需记录日志时,还好;然而,当程序的状态取决于它时,则会造成完全混乱和恐慌。在后一种情况下,请始终选择同步模式。

1
+1 而且它们甚至比那更好:通常不需要内核线程(大多数驱动程序甚至没有线程)。 - Stephen Cleary

2

您不希望阻塞用户界面。使用异步操作,您可以在等待服务器响应时执行其他操作。


2

异步模式让您在处理过程中继续进行,而同步模式则需要等待。


简短明了的回答,但足够解释清楚。谢谢@Brian。 - Prerna Jain

1

1

跟随Hans的回答:I/O操作与线程的独立性允许更显著的扩展;可以处理成千上万个未完成请求,这是使用线程无法实现的。

此外,当您开始考虑协议设计中错误处理的复杂性时,异步方法的复杂度要比正确地编写同步代码的复杂度小得多。大多数同步套接字代码看起来很简单,但实际上包含微妙的错误。

如果双方发送的数据超过读取的数据,异步方法也很重要,以防止死锁情况;有关更多讨论,请参见此博客文章

如果您想要异步I/O的可靠性和(大部分)性能优势,并带有线程安全的包装器(具有更简单的错误处理),请考虑使用Nito.Async库。


0
在同步服务器中,访问数据结构(如果存在更新)时必须处理锁定,这需要时间和代码(并且是难以找到错误的来源)。同时,在许多实现中拥有许多线程(例如堆栈分配)会带来技术问题,如果服务器受到IO限制,则这些线程几乎都处于睡眠状态(等待网络),浪费内存。
在单个线程上使用异步模型可以忽略锁定问题(这意味着您的处理速度可以达到最快),并且仅使用客户端实际需要的内存(只有一个堆栈)。
然而,现在多核机器非常常见,因此部分优势已经丧失(因为如果修改共享数据结构,必须进行锁定)。可能最佳效率可通过在 N 个异步服务器之前使用负载均衡器获得,其中 N 是您环境中的最佳线程数。
异步方法的缺点是,根据所使用的工具,代码可能相当丑陋且难以理解,并且如果计算不是微不足道的,并且由于错误导致整个异步服务器进入无尽循环,那么整个异步服务器将无响应(因此可能需要添加看门狗程序)。

异步并不能解决锁定问题。您的异步回调可能会被来自不同线程的多个回调重新进入。在使用异步模型时,仍需要保护任何共享状态。 - heavyd
使用抢占式多线程编写正确的锁定代码的问题在于切换是“不可见的”,并且可以在任何时候发生。 异步实现中的切换点是固定的,通常绝对明显。此外,由于确定性,调试要简单得多。 虽然仍然可能存在某种形式的“逻辑”锁定,但我认为这与在共享数据更新方法中正确获取锁定的难度相比微不足道。 - 6502
@heavyd:重新阅读您的评论,我觉得术语上存在问题。我说单线程异步实现可以省去锁定问题……(指并发访问的物理锁定,而非自动机状态变化之间的逻辑锁定当然是必需的),如果异步不与单线程耦合,则此优势将丧失。在您的评论中,您谈到了不同的线程,因此可能存在术语上的误解。 - 6502

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