简体   繁体   English

为什么阻塞线程要消耗比异步/等待更多的时间?

[英]Why does a blocking thread consume more then async/await?

See this question and answer; 看到这个问题和答案;

Why use async controllers, when IIS already handles the request concurrency? 当IIS已经处理请求并发时,为什么要使用异步控制器?

Ok, a thread consumes more resources then the async/await construction, but why ? 好的,线程比异步/等待构造消耗更多的资源,但是为什么呢? What is the core difference? 核心区别是什么? You still need to remember all state etc, don't you? 您仍然需要记住所有状态,不是吗?

Why would a thread pool be limited, but can you have tons of more idle async/await constructions? 为什么要限制线程池,但是您可以拥有更多的空闲异步/等待构造吗?

Is it because async/await knows more about your application? 是否因为async / await更了解您的应用程序?

Creating a new thread allocates a separate memory area exclusive for this thread holding its resources, mainly its call stack which in Windows takes up 1MB of memory. 创建一个新线程会为此线程分配一个单独的内存区域,该线程区域专用于保留其资源,该线程主要是其调用堆栈,在Windows中占用1MB内存。

So if you have a 1000 idle threads you are using up at least 1GB of memory doing nothing. 因此,如果您有1000个空闲线程,那么您至少要占用1GB的内存而无所作为。

The state for async operations takes memory as well but it's just the actual size needed for that operation and the state machine generated by the compiler and it's kept on the heap. 异步操作的状态也会占用内存,但这只是该操作和编译器生成的状态机所需的实际大小,并保留在堆中。

Moreover, using many threads and blocking them has another cost (which IMO is bigger). 而且,使用许多线程并阻塞它们会带来另一笔开销(IMO会更大)。 When a thread is blocked it is taken out of the CPU and switched with another (ie context-switch). 当一个线程被阻塞时,它将被带出CPU并与另一个线程进行切换(即上下文切换)。 That means that your threads aren't using their time-slices optimally when they get blocked. 这意味着您的线程在被阻塞时并未最佳地使用其时间片。 Higher rate of context switching means your machine does more overhead of context-switching and less actual work by the individual threads. 较高的上下文切换速率意味着您的计算机将执行更多的上下文切换开销,并减少各个线程的实际工作。

Using async-await appropriately enables using all the given time-slice since the thread, instead of blocking, goes back to the thread pool and takes another task to execute while the asynchronous operation continues concurrently. 适当地使用async-await可以启用所有给定的时间片,因为线程将返回阻塞池,而不是阻塞,而是在异步操作同时继续执行的同时执行另一个任务。

So, in conclusion, the resources async await frees up are CPU and memory, which allows your server to handle more requests concurrently with the same amount of resources or the same amount of requests with less resources. 因此,总而言之,异步等待释放的资源是CPU和内存,这使您的服务器可以使用相同数量的资源或更少资源的同时处理更多请求。

Well, let's imagine a web-server. 好吧,让我们想象一下一个网络服务器。 Most of his time, all he does is wait. 在他大部分时间里,他所做的只是等待。 it doesn't really CPU-bound usually, but more of I/O bound. 通常,它实际上并没有受CPU限制,而是有更多的I / O限制。 It waits for network I/O, disk I/O etc. After every time he waits, he has something (usually very short to do) and then all he does is waiting again. 它等待网络I / O,磁盘I / O等。每次等待之后,他都有一些事情(通常做的很短),然后他所做的只是再次等待。 Now, the interesting part is what happend while he waits . 现在,有趣的部分是他等待时发生了什么。 In the most "trivial" case (that of course is absolutely not production), you would create a thread to deal with every socket you have. 在最“琐碎”的情况下(当然绝对不是生产情况),您将创建一个线程来处理您拥有的每个套接字。

Now, each of those threads has it's own cost. 现在,每个线程都有其自己的成本。 Some handles, 1MB of stack space... And of course, not all those threads can run in the same time - so the OS scheduler need to deal with that and choose the right thread to run each time (which means A LOT of context switching). 一些句柄,1 MB的堆栈空间...当然,并非所有这些线程都可以同时运行-因此OS调度程序需要处理该问题并选择每次运行的正确线程(这意味着有很多上下文交换)。 It will work for 1 clients. 它将为1个客户工作。 It'll work for 10 clients. 适用于10位客户。 But, let's imagine 10,000 clients at the same time. 但是,让我们假设同时有10,000个客户。 10,000 threads means 10GB of memory. 10,000个线程表示10GB内存。 That's more than the average web server in the world. 这比世界上普通的Web服务器还要多。

All of these resources, is because you dedicated a thread for a user. 所有这些资源,是因为您为用户专用了一个线程。 BUT, most of this threads does nothing! 但是,大多数这些线程什么都不做! they just wait for something to happen. 他们只是在等待某些事情发生。 and the OS has API for async IO that allows you to just queue an operation that will be done once the IO operation completed, without having dedicated thread waiting for it. 并且操作系统具有用于异步IO的API,该API允许您仅将要在IO操作完成后执行的操作排队,而无需专用线程等待它。

If you use async/await, you can write application that will easily use less threads, and each of the thread will be utilized much more - less "doing nothing" time. 如果使用async / await,则可以编写将轻松使用更少线程的应用程序,并且每个线程将被更多地利用-更少的“无所事事”时间。

async/await is not the only way of doing that. 异步/等待不是唯一的方法。 You could have done this before async/await was introduced. 您可以在引入异步/等待之前完成此操作。 BUT, async/await allows you to write code that's very readable and very easy to write that does that, and look almost as it runs just on a single thread (not a lot of callbacks and delegates moving around like before). 但是,async / await允许您编写可读性强且易于编写的代码,并且看上去几乎只在单个线程上运行(而不像以前那样有很多回调和委托在运行)。

By combining the easy syntax of async/await and some features of the OS like async I/O (by using IO completion port), you can write much more scalable code, without losing readability. 通过将异步/等待的简单语法与操作系统的某些功能(如异步I / O)(通过使用IO完成端口)相结合,可以编写更多可伸缩的代码,而不会失去可读性。

Another famous sample is WPF/WinForms. 另一个著名的示例是WPF / WinForms。 You have the UI thread, that all he does is to process events, and usually has nothing special to do. 您拥有UI线程,他所做的只是处理事件,通常没有什么特别的事情。 But, you can't block it or the GUI will hang and the user won't like it. 但是,您不能阻止它,否则GUI会挂起,并且用户将不喜欢它。 By using async/await and splitting each "hard" work to short operations, you can achieve responsible UI and readable code. 通过使用异步/等待并将每个“艰苦”的工作分解为简短的操作,您可以实现负责任的UI和可读代码。 If you have to access the DB to execute a query, you'll start the async operation from the UI thread, and then you'll "await" it until it ends and you have results that you can process in the UI thread (because you need to show them to the user, for example). 如果您必须访问数据库以执行查询,则将从UI线程开始异步操作,然后“等待”它直到结束,并且您可以在UI线程中处理结果(因为例如,您需要将其显示给用户)。 You could have done it before, but using async/await makes it much more readable. 您以前可能已经做过,但是使用async / await使其更具可读性。

Hope it helps. 希望能帮助到你。

The important thing to realize here is that a blocked thread is not usable to do any other work until it becomes unblocked. 在这里要意识到的重要一点是,阻塞线程在解除阻塞之前不可用于执行任何其他工作。 A thread that encounters an await is free to return to the threadpool and pick up other work until the value being awaited becomes available. 遇到等待的线程可以自由返回线程池并进行其他工作,直到等待的值可用为止。

When you call a synchronous I/O method, the thread executing your code is blocked waiting for the I/O to complete. 当您调用同步I / O方法时,执行代码的线程将被阻塞,等待I / O完成。 To handle 1000 concurrent requests, you will need 1000 threads. 要处理1000个并发请求,您将需要1000个线程。

When you call an asynchronous I/O method, the thread is not blocked. 当您调用异步I / O方法时,线程不会被阻塞。 It initializes the I/O operation and can work on something else. 它初始化I / O操作并可以处理其他事情。 It can be the rest of your method (if you don't await ), or it can be some other request if you await the I/O method. 它可以是方法的其余部分(如果不await ),或者可以是其他一些请求,如果您await I / O方法。 The thread pool doesn't need to create new threads for new requests, as all the threads can be used optimally and keep the CPUs busy. 线程池不需要为新请求创建新线程,因为所有线程都可以得到最佳利用,并使CPU保持繁忙。

Async I/O operations are actually implemented asynchronously at the OS level . 异步I / O操作实际上是在OS级别异步实现的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM