为什么阻塞线程比async/await消耗更多



查看此问答;

当IIS已经处理请求并发时,为什么要使用异步控制器?

好吧,线程比async/await构造消耗更多的资源,但为什么?核心区别是什么?你还需要记住所有的州等等,不是吗?

为什么线程池会受到限制,但你能有更多空闲的异步/等待构造吗?

是因为async/await对您的应用程序了解更多吗?

好吧,让我们想象一个web服务器。大部分时间,他所做的就是等待。它通常并没有真正的CPU绑定,但更多的是I/O绑定。它等待网络I/O、磁盘I/O等。每次等待后,他都会有一些事情(通常很短的时间要做),然后他所做的就是再次等待。现在,有趣的部分是他等待时发生了什么。在最"琐碎"的情况下(当然这绝对不是生产),您将创建一个线程来处理您拥有的每个套接字。

现在,每个线程都有自己的成本。一些句柄,1MB的堆栈空间。。。当然,并不是所有这些线程都能在同一时间运行,所以操作系统调度程序需要处理这一问题,并每次选择正确的线程运行(这意味着要进行大量的上下文切换)。它适用于1个客户。它适用于10个客户。但是,让我们想象一下同时有10000个客户。10000个线程意味着10GB的内存。这比世界上平均的网络服务器还多。

所有这些资源,都是因为您为用户专用了一个线程。但是,大多数线程都没有任何作用!他们只是在等待什么事情发生。操作系统具有异步IO的API,它允许您只对IO操作完成后将要执行的操作进行排队,而无需专用线程等待

如果您使用async/await,那么您可以编写使用更少线程的应用程序,并且每个线程都将使用更少的"无所事事"时间。

async/await并不是唯一的方法。在引入async/await之前,您本可以做到这一点。但是,async/await允许您编写可读性强、易于编写的代码,并且看起来几乎就像它只在一个线程上运行一样(不像以前那样有很多回调和委托在移动)。

通过结合async/await的简单语法和操作系统的一些功能,如异步I/O(通过使用IO完成端口),您可以在不失去可读性的情况下编写更具可扩展性的代码。

另一个著名的例子是WPF/WinForms。你有UI线程,他所做的只是处理事件,通常没有什么特别的事情要做。但是,你不能阻止它,否则GUI会挂起,用户不会喜欢它。通过使用async/await并将每个"艰苦"的工作分解为短操作,你可以实现负责任的UI和可读的代码。如果您必须访问DB才能执行查询,那么您将从UI线程启动异步操作,然后"等待"它,直到它结束,并且您可以在UI线程中处理结果(例如,因为您需要将结果显示给用户)。您以前本可以这样做,但使用async/await使其可读性更强。

希望能有所帮助。

创建一个新线程会为该线程分配一个单独的内存区域,该内存区域专门用于保存其资源,主要是其调用堆栈,在Windows中占用1MB的内存。

因此,如果您有1000个空闲线程,那么您将消耗至少1GB的内存,而不执行任何操作

异步操作的状态也占用内存,但它只是该操作和编译器生成的状态机所需的实际大小,并保存在堆中。

此外,使用许多线程并阻塞它们还有另一个成本(IMO更大)。当一个线程被阻塞时,它会从CPU中取出,并与另一个线程进行切换(即上下文切换)。这意味着当线程被阻塞时,它们并没有以最佳方式使用它们的时间片更高的上下文切换速率意味着您的机器要做更多的上下文切换开销,而单个线程的实际工作更少

使用异步等待可以适当地使用所有给定的时间片,因为线程不是阻塞,而是返回到线程池,并在异步操作同时继续时执行另一个任务。

因此,总之,异步等待释放的资源是CPU和内存,这允许您的服务器用相同的资源量同时处理更多的请求,或者用更少的资源处理相同的请求量

这里要认识到的重要一点是,被阻塞的线程在被解除阻塞之前无法执行任何其他工作。遇到等待的线程可以自由返回到线程池并承担其他工作,直到等待的值可用为止。

当您调用同步I/O方法时,执行代码的线程会被阻塞,等待I/O完成。要处理1000个并发请求,需要1000个线程。

当您调用异步I/O方法时,线程不会被阻塞。它初始化I/O操作,并且可以处理其他操作。它可以是方法的其余部分(如果没有await),也可以是其他请求(如果await是I/O方法)。线程池不需要为新请求创建新线程,因为所有线程都可以得到最佳使用,并使CPU保持繁忙。

异步I/O操作实际上是在操作系统级别异步实现的。

最新更新