为什么使用异步请求而不是使用更大的线程池?

2024-01-04

在荷兰的 Techdays 期间,Steve Sanderson 做了关于C#5、ASP.NET MVC 4 和异步 Web. http://channel9.msdn.com/Events/TechDays/Techdays-2012-the-Netherlands/2287

他解释说,当请求需要很长时间才能完成时,线程池中的所有线程都会变得繁忙,新的请求必须等待。服务器无法处理负载,一切都会变慢。

然后,他展示了使用异步 Web 请求如何提高性能,因为工作随后会委托给另一个线程,并且线程池可以快速响应新传入的请求。他甚至对此进行了演示,并表明 50 个并发请求首先需要 50 * 1 秒,但异步行为总共只需要 1.2 秒。

但看完之后我还是有一些疑问。

  1. 为什么我们不能使用更大的线程池?使用 async/await 启动另一个线程不是比从一开始就增加线程池慢吗?不是说我们运行的服务器突然多了线程什么的吗?

  2. 来自用户的请求仍在等待异步线程完成。如果池中的线程正在做其他事情,那么“UI”线程如何保持忙碌?史蒂夫提到了“一个知道某事何时完成的智能内核”。这是如何运作的?


这是一个非常好的问题,理解它是理解为什么异步 IO 如此重要的关键。 C# 5.0 中添加新的 async/await 功能的原因是为了简化异步代码的编写。然而,对服务器上异步处理的支持并不是什么新鲜事,它自 ASP.NET 2.0 以来就存在。

正如 Steve 向您展示的那样,通过同步处理,ASP.NET(和 WCF)中的每个请求都会从线程池中获取一个线程。他演示的问题是一个众所周知的问题,称为“线程池饥饿如果你在你的服务器上进行同步IO,线程池线程将在IO持续时间内保持阻塞(不做任何事情)。由于线程池中的线程数量有限制,在负载下,这可能会导致在所有线程池线程都被阻塞等待IO的情况下,请求开始排队,导致响应时间增加。由于所有线程都在等待IO完成,你会看到CPU占用接近0 %(即使响应时间非常长)。

你要问什么(为什么我们不能使用更大的线程池?)是一个非常好的问题。事实上,到目前为止,大多数人都是这样解决线程池饥饿问题的:只需在线程池上添加更多线程即可。 Microsoft 的一些文档甚至指出,这是对可能发生线程池饥饿情况的修复。这是一个可以接受的解决方案,在 C# 5.0 之前,这样做比将代码重写为完全异步要容易得多。

但该方法存在一些问题:

  • 没有一种价值适用于所有情况:您需要的线程池线程数线性取决于 IO 的持续时间和服务器上的负载。不幸的是,IO 延迟大多是不可预测的。这是一个例子: 假设您向 ASP.NET 应用程序中的第三方 Web 服务发出 HTTP 请求,大约需要 2 秒才能完成。您遇到线程池匮乏的情况,因此您决定将线程池大小增加到 200 个线程,然后它再次开始正常工作。问题是,也许下周,Web 服务就会出现技术问题,导致响应时间增加到 10 秒。突然间,线程池饥饿又回来了,因为线程被阻塞的时间延长了 5 倍,因此您现在需要将数量增加 5 倍,达到 1,000 个线程。

  • 可扩展性和性能:第二个问题是,如果这样做,每个请求仍然会使用一个线程。线程是一种昂贵的资源。 .NET 中的每个托管线程都需要为堆栈分配 1 MB 的内存。对于持续 5 秒的网页,每秒有 500 个请求的负载,您的线程池中将需要 2,500 个线程,这意味着需要 2.5 GB 内存用于不执行任何操作的线程堆栈。然后你就会遇到上下文切换的问题,这会对你的机器的性能造成严重影响(影响机器上的所有服务,而不仅仅是你的 Web 应用程序)。尽管 Windows 在忽略等待线程方面做得相当好,但它并不是为处理如此大量的线程而设计的。请记住,当运行的线程数等于计算机上逻辑 CPU 的数量(通常不超过 16)时,效率最高。

因此,增加线程池的大小是一种解决方案,人们已经这样做了十年(甚至在微软自己的产品中),只是在内存和CPU使用方面,它的可扩展性和效率较低,而且你总是在IO 延迟突然增加会导致饥饿。在 C# 5.0 之前,异步代码的复杂性对于很多人来说并不值得。 async/await 改变了一切,就像现在一样,您可以受益于异步 IO 的可扩展性,同时编写简单的代码。

更多细节:http://msdn.microsoft.com/en-us/library/ff647787.aspx http://msdn.microsoft.com/en-us/library/ff647787.aspx "当 Web 服务调用继续时有机会执行其他并行处理时,可以使用异步调用来调用 Web 服务或远程对象。尽可能避免对 Web 服务的同步(阻塞)调用,因为传出 Web 服务调用是使用 ASP.NET 线程池中的线程进行的。阻塞调用会减少用于处理其他传入请求的可用线程数量。"

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么使用异步请求而不是使用更大的线程池? 的相关文章

随机推荐